Varför webbkartan blir långsam — och vad som faktiskt hjälper

·Av OrbGIS-teamet

Ett lager med miljontals objekt som hackar i webbläsaren beror sällan på för svag hårdvara. Här är vad som verkligen gör webbkartor långsamma — hela filer, WMS-anrop och tunga rutor — och hur vektortiles och generalisering löser det.

Lagret fungerar utmärkt i QGIS. Du publicerar det, öppnar webbkartan, och rikstäckande vy snurrar i femton sekunder innan den målar upp en grå rektangel. Zoomar du in är allt bra igen. Zoomar du ut tar fliken två gigabyte minne och dör.

Det här är den vanligaste besvikelsen i webbGIS, och den beror nästan aldrig på att servern är för liten. Den handlar om hur mycket data som går över nätet, och hur mycket av den som lever kvar i webbläsarens minne när den väl har kommit fram. Två tal som de flesta aldrig har mätt, helt enkelt för att desktop-GIS aldrig har tvingat oss att göra det.

Här är vad som faktiskt avgör om en stor datamängd går att använda på webben, i den ordning flaskhalsarna dyker upp.

Det korta svaret#

En webbkarta är långsam av något av fyra skäl, och de har olika lösningar:

  1. För många byte över nätet. Klienten laddar ner hela datamängden, eller en ruta som är mycket tyngre än den behöver vara. Lösningen är att leverera tiles, och att generalisera det som de låga zoomnivåerna innehåller.
  2. För många objekt att tolka i webbläsaren. Varje objekt som kommer fram blir ett JavaScript-objekt som ska avkodas, symbolsättas, träffprövas och sedan sparas i minnet. Lösningen är att minska antalet objekt per ruta, inte bara antalet byte.
  3. För mycket arbete per anrop på servern. En utzoomad fråga som rör varje rad i en tabell med miljontals rader hinner inte bli klar hur väl rutan än komprimeras. Lösningen är färdigbyggda översikter och ett spatialt index som verkligen används.
  4. Fel leveransmodell för uppgiften. Vissa format kan helt enkelt inte bli snabba i den här storleken, hur mycket du än trimmar dem.

De flesta ärenden om ”långsam karta” är i själva verket de två första, förklädda till det fjärde.

Objekt är fel måttstock — räkna brytpunkter#

”Hur många objekt klarar en webbkarta?” är frågan alla ställer, och den har inget användbart svar. Tvåhundratusen busshållplatser och tvåhundratusen kustlinjepolygoner är inte samma arbetsbörda, inte ens i samma storleksordning.

Måttet som förutsäger prestanda är brytpunkten, alltså koordinatparet. En punkt har en. En kommungräns har ofta tiotusentals. Ett enda detaljerat naturreservat kan bära fler brytpunkter än hundratusen adresspunkter tillsammans.

Innan du skyller på plattformen är det alltså två tal du vill ha om lagret: antalet objekt och det totala antalet brytpunkter. Dividera dem så får du brytpunkter per objekt. I PostGIS:

SELECT count(*) AS objekt,
       sum(ST_NPoints(geom)) AS brytpunkter,
       sum(ST_NPoints(geom)) / count(*) AS per_objekt
FROM ditt_lager;

Under ungefär 10 brytpunkter per objekt har du punkter eller enkla rutor, och då är antalet objekt det som spelar roll. Över ungefär 200 har du konturmonster — kustlinjer, Natura 2000-områden, administrativa gränser — och då dominerar brytpunkterna allt, inklusive det du aldrig ser på skärmen.

Ett exempel på skalan: den europeiska cykelledsdatamängd vi använder som stresstest innehåller 115 000 ledrelationer och 41 miljoner brytpunkter. Ingenting i det lagret löser sig med en snabbare server.

Tre sätt att få geodata till en webbläsare#

Skicka hela filen#

GeoJSON direkt i sidan, eller en shapefil som konverterats till GeoJSON på vägen ut. Webbläsaren laddar ner hela datamängden innan den första bildpunkten ritas, tolkar allt, och behåller allt i minnet — oavsett om det syns på skärmen eller inte.

Det fungerar faktiskt utmärkt upp till några tusen enkla objekt, och det är frestande just för att det är så enkelt. Men kostnaden har inget tak: en GeoJSON på 300 MB är en nedladdning på 300 MB och ungefär en gigabyte levande objekt i minnet, på varje zoomnivå — även den där hela datamängden är fyra bildpunkter bred.

Låt servern rita (WMS)#

Servern renderar bilder och skickar bilder. Klientens kostnad är konstant hur stor datamängden än är, och det är precis därför WMS har överlevt tjugofem år av allt större data.

Priset är allt som händer efter att bilden har ritats. Symbolsättningen bor på servern, så ett färgbyte är ett nytt anrop, eller ett ärende till någon annan. Det finns ingen filtrering i klienten, ingen hovring, och ingen klickinformation utan ett extra GetFeatureInfo-anrop per klick. Etiketter kan inte flytta på sig. En vanlig WMS renderar dessutom vid varje anrop, så varje panorering är nytt arbete för servern — det är därför WMTS med färdigrenderade, cachade rutor finns, och därför den är så mycket snabbare, mot priset av låst symbolsättning och en cache som ska rensas.

Dela upp geometrin i vektortiles#

Själva geometrin klipps upp i en rutpyramid och levereras som kompakta binära rutor (MVT). Klienten hämtar bara de rutor som täcker den aktuella vyn på den aktuella zoomnivån, och ritar dem själv.

Den sista delen är hela poängen. Symbolsättningen sker i klienten, så ett färgbyte slår igenom direkt utan att något hämtas om. Objekten är riktiga objekt, så hovring, klick, popuper och filtrering fungerar på data som webbläsaren redan har — ett filter i en dashboard kan plocka bort objekt ur kartan helt utan serveranrop. Etiketter flyttar på sig. Rutorna cachas som bilder.

Kostnaden flyttar i stället till två nya ställen: någon måste bygga rutorna, och en ruta på en låg zoomnivå kan bli enorm. Och där ligger hela problemet.

Hela filen (GeoJSON)WMS/WMTS (raster)Vektortiles (MVT)
Överförd dataHela datamängdenEn bild per ruta eller vyBara synliga rutor
Klientens kostnad vid skalaVäxer med datamängdenKonstantVäxer med rutornas vikt
Byta symbolsättningDirekt, allt finns i minnetNytt serveranropDirekt
Klick, hovring och popuperJaGetFeatureInfo per klickJa
Filtrering i klientenJaNejJa
Praktiskt takNågra tusen objektMycket högtMycket högt, med generalisering
Typiskt haveriNedladdning och minneRenderingstid, ingen interaktivitetTunga rutor långt ut

Det är den utzoomade rutan som fäller kartan#

Zoomnivå 14 är enkelt: en ruta täcker några kvarter och innehåller därför en handfull objekt, hur stort lagret än är. Zoomnivå 4 är problemet — där täcker en enda ruta en halv kontinent, och en naiv implementation lägger hela datamängden i den.

Den rutan ska hinna byggas inom en hård tidsgräns på servern (en fråga som drar över tiden avbryts, och en avbruten ruta är en tom ruta), komprimeras, skickas, tolkas och ritas. Samtidigt är varje objekt i den mindre än en bildpunkt: i den skalan är en byggnad mindre än pricken som ritar den. Du betalar alltså för att skicka detaljer som är fysiskt omöjliga att se.

Det är den insikten hela hantverket vilar på. Det kartan visar utzoomad ska inte vara samma data som den visar inzoomad. Det ska vara en sanningsenlig sammanfattning, och den ska vara liten.

Generalisering: gör de låga zoomnivåerna ärliga och lätta#

Det finns bara fyra saker att ta bort ur en ruta, och en bra plattform använder alla fyra, på olika zoomnivåer.

Färre brytpunkter. Douglas–Peucker eller något liknande, med toleransen satt till ungefär en bildpunkt på den aktuella zoomnivån. Alla objekt är kvar, bara formen blir grövre — och under en bildpunkt är grövre osynligt. Det här är rätt behandling för konturtunga polygoner.

Här finns en fälla som de flesta går i: förenkling kan inte ta bort ändpunkterna på delgeometrier. I vårt cykelledsnät med 115 000 leder hade varje led i snitt omkring 26 separata delar, en per ingående vägsträcka, och enbart delarnas ändpunkter dominerade rutans vikt så att förenklingen knappt bet. Att sy ihop delarna innan förenklingen tog en tät ruta på zoomnivå 5 från 15,6 MB till 0,4 MB i våra egna mätningar. Om din förenkling ”inte gör någonting” är det alltså antalet delar du ska titta på, inte antalet brytpunkter.

Färre objekt. Under den zoomnivå där medianobjektet är en bildpunkt brett är det inte bara dyrt att rita ut varje objekt, det är missvisande: du får en kompakt smet som döljer precis den täthet den påstår sig visa. Lägg i stället objekten i celler och bär med ett antal och några summeringar. Görs det rätt följer de låga zoomnivåerna datamängdens verkliga siluett och säger mer än råmaterialet hade gjort.

Färre attribut. Den som glöms bort. På mellanzoomarna är det ofta attributen som väger, inte geometrin — hela källans kolumnuppsättning upprepad för varje objekt. Att gå ner till en handfull kolumner med få unika värden, på de zoomnivåer där ingen ändå klickar, gav ungefär 7 gånger mindre rutor i vår egen kedja, med exakt samma geometri.

En helt annan sammanfattning. För likformiga klassrutnät — marktäcke, urbaniseringsgrad, allt som är miljontals identiska rutor med en klass — fungerar ingen av metoderna ovan: täthetsräkningen blir platt eftersom varje cell är ett objekt, och även med bantade attribut återstår 4,7 miljoner polygoner. Den enda ärliga sammanfattningen är den vanligaste klassen per grövre cell, alltså vektormotsvarigheten till majoritetsomsampling av raster. Det är samtidigt en bedömning av vad datan betyder, inte en tumregel: ”vanligaste marktäckeklassen per kvadratkilometer” är sant och användbart, ”vanligaste fastighetsägaren” är varken eller. Sådan aggregering ska alltid vara ett aktivt val.

Tips

Generalisering är ett beslut om presentationen. Källdatan ska aldrig ändras för att kartan ska bli snabb — översikterna är extra artefakter, och alla exporter, nedladdningar och analyser arbetar fortfarande mot originalet i full upplösning.

Webbläsaren är den andra halvan av problemet#

Storleken i byte är det tal alla optimerar, och det är bara halva sanningen. En vektorruta som komprimeras till 500 kB förblir inte 500 kB i minnet. Tolkad blir varje objekt ett geometriobjekt, koordinatlistor, ritinstruktioner per lager och kopior för träffprövning.

Vi har mätt ungefär 3,5 kB kvar i minnet per objekt i webbläsaren för ett tätt klassrutnät. Det låter försumbart tills man multiplicerar: en vy som landat med tjugo rutor med omkring 98 000 objekt vardera håller ungefär 3,4 GB levande objekt, alltså nära gränsen där webbläsaren stänger fliken. Rutan var i sin ordning. Servern var i sin ordning. Kartan dog ändå.

Därför behöver en seriös kartmotor också hushålla med minnet i klienten: kasta ut rutor som lämnat vyn, sätta ett tak för hur mycket som får ligga kvar, och bromsa inläsningen så att tolkningen inte springer i förväg. Och därför måste storleken på en utzoomad ruta ha ett tak redan när den byggs — mätt i objekt, inte bara i byte.

En checklista som fungerar oavsett plattform#

  1. Mät brytpunkter, inte bara objekt — och kolla antalet delgeometrier per objekt på linje- och multipolygonlager.
  2. Lagra datan i ett projicerat koordinatsystem i meter, med spatialt index. Avstånd i grader är inte likformiga, och en förenklingstolerans i grader betyder ingenting. Vilket system du bör välja är en artikel i sig.
  3. Leverera tiles i stället för filer för allt över några tusen objekt.
  4. Testa den värsta rutan först. Zooma ut hela vägen, öppna nätverksfliken och titta på storleken och tiden för den tyngsta rutan. Det enda talet förutsäger nästan allt.
  5. Banta attributen på låga zoomnivåer. Ingen klickar på ett objekt som är en fjärdedels bildpunkt brett.
  6. Sätt ett tak för antalet objekt per ruta, inte bara för antalet byte — det är objekten webbläsarens minne följer.
  7. Kontrollera resultatet på en vanlig bärbar dator, inte på arbetsstationen du byggde datan på. Det är den vanliga datorn dina användare sitter vid.
  8. Mät aldrig ytor och avstånd i kartans visningsprojektion. Det är ett riktighetsfel snarare än ett prestandafel, och ett betydligt dyrare misstag.

Så gör vi i OrbGIS#

Vi har byggt hela uppladdningskedjan runt just de här haverierna, eftersom datamängderna våra användare kommer med — rikstäckande marktäcke, alla byggnader i en kommun, Natura 2000, kompletta ledningsnät — knäcker naiva lösningar redan vid första uppladdningen.

  • Varje uppladdning blir ett riktigt lager hos oss: typade kolumner, spatialt index, beräknad utbredning och vektortiles från vår egen tileserver. Det finns ingen GeoJSON-väg i klienten för lagerdata, så det finns heller ingen storlek där kartan tyst faller tillbaka på något som inte klarar uppgiften.
  • Stora lager får automatiskt en detaljnivåpyramid, byggd vid uppladdningen när lagret passerar ungefär 100 000 objekt eller bär mycket tung geometri (omkring 5 miljoner brytpunkter). Det finns ingen inställning att leta rätt på och inget beslut att fatta. Punkter, ytor och linjenät får var och en den behandling som passar dem, inklusive ihopsyningen av delgeometrier som beskrivs ovan.
  • Valet av zoomband sker på serversidan. Tileservern avgör vilket färdigbyggt band som svarar på varje anrop, så rätt data kommer fram oavsett vilken klient som frågar, och ett band kan byggas om utan att någon klient behöver uppdateras.
  • Budgetarna för rutorna är uppmätta, inte formler — inklusive taket för webbläsarens minne ovan. Det är därför överlämningen till full upplösning sker där en landad vy med god marginal ryms i en vanlig bärbar dator.
  • Datasetsidan visar vad som faktiskt byggts. Kortet Renderingspyramid listar banden som används, och erbjuder tillvalet Mode-aggregering för klassrutnät. Skulle en översikt någon gång misslyckas fortsätter lagret att levereras från källan, och det syns tydligt i gränssnittet i stället för att rutorna tyst blir tomma.
  • Dashboards filtrerar i klienten. Filter, klick i diagram och val i tabeller plockar bort objekt ur kartan med hjälp av rutor webbläsaren redan har — ingen ny fråga, ingen omladdning.
  • Redigeringar syns direkt. Det du sparar från QGIS-insticket syns omedelbart på detaljerade zoomnivåer, och köar samtidigt en uppdatering av översikterna så att de utzoomade banden hinner ifatt inom några minuter.

Ingenting av det här är märkvärdigt. Det är den uppsättning tekniker som hela tilevärlden har landat i — skillnaden ligger i om du ska bygga dem själv för varje datamängd, eller om de sker av sig själva när du släpper filen.

Vanliga frågor#

Varför är webbkartan långsam när jag zoomar ut, men snabb när jag zoomar in?#

För att en utzoomad ruta täcker så mycket större yta. På hög zoomnivå innehåller en ruta en handfull objekt hur stort lagret än är, medan en naiv lösning lägger hela datamängden i en enda ruta på låg zoomnivå. Den rutan ska hämtas, komprimeras, skickas, tolkas och ritas — oftast medan varje objekt i den är mindre än en bildpunkt. Lösningen är färdigbyggda översikter, alltså en detaljnivåpyramid som levererar en lättare och generaliserad sammanfattning längst ut.

Hur många objekt klarar en webbkarta?#

Det finns inget fast tal, eftersom objekt inte är det som sätter gränsen — det gör brytpunkter och byte per ruta. Tvåhundratusen punkter beter sig helt annorlunda än tvåhundratusen detaljerade ytor. Som tumregel: några tusen objekt går bra som ren GeoJSON i sidan, därutöver vill du ha vektortiles, och över ungefär 100 000 objekt eller några miljoner brytpunkter vill du ha generaliserade översikter för de låga zoomnivåerna. Med det på plats är tiotals miljoner objekt vardagsmat.

Vektortiles eller WMS — vad ska jag välja?#

WMS eller WMTS om kartan är en bild: låst kartografi, ingen interaktion, och kanske en renderingsmotor du inte kan byta ut. Vektortiles för allt som ska vara interaktivt — symbolsättning i klienten, direkta stilbyten, hovring, popuper och filtrering utan serveranrop. Vektortiles skalar dessutom bättre i klienten eftersom bara de synliga rutorna någonsin överförs, men de behöver generaliserade låga zoomnivåer för att hålla farten.

Är GeoJSON dåligt för stora datamängder?#

Inte dåligt, bara utan tak. GeoJSON är utförlig text utan spatialt index och utan rutindelning, så klienten laddar ner och tolkar allt innan något visas, och behåller sedan allt i minnet. Det är utmärkt för några tusen enkla objekt. För lagring och utbyte av stora datamängder är ett binärt format som GeoPackage, FlatGeobuf eller GeoParquet betydligt effektivare, och för att visa stora datamängder ska du använda tiles oavsett vilket källformat du har.

Vad betyder generalisering och detaljnivåer (LOD) i en webbkarta?#

Att medvetet leverera förenklade eller sammanfattade versioner av en datamängd på de zoomnivåer där full detaljrikedom ändå är osynlig. I praktiken handlar det om färre brytpunkter per objekt, färre objekt genom täthetsceller eller gallring, färre attributkolumner, eller en verklig sammanfattning som vanligaste klass per cell — valt per zoomband och inväxlat automatiskt när du zoomar. Görs det bra blir kartan både snabbare och mer läsbar, eftersom en smet av objekt under bildpunktsstorlek inte berättar någonting.

Måste jag förenkla min data innan jag laddar upp den?#

Det ska du inte behöva, och oftast vill du inte heller: att förenkla källan är destruktivt och går inte att ångra, och du kommer att behöva den fullständiga geometrin för analys, export och utskrift. Generaliseringen hör hemma i renderingskedjan, som extra artefakter härledda ur ett orört original. I OrbGIS sker det automatiskt vid uppladdningen för stora lager.

Förändrar generaliseringen min data?#

Nej. Översiktsbanden är egna tabeller som byggs vid sidan av källan och används bara för att svara på ruthämtningar på låga zoomnivåer. Exporter, Dataredigeraren, turen till QGIS och tillbaka, attributfrågor och utskrifter läser alla originalet i full upplösning. Zoomar du in förbi överlämningsnivån ser du exakt geometri igen.

Varför drar kartan så mycket minne, eller kraschar fliken?#

För att tolkade vektorobjekt kostar betydligt mer i minne än de gör över nätet — geometriobjekt, koordinatlistor, ritinstruktioner och strukturer för träffprövning, i våra mätningar omkring 3,5 kB per objekt. Tjugo tunga rutor på skärmen kan därför hålla flera gigabyte levande objekt trots att varje ruta laddades ner på långt under en sekund. Att sätta tak för antalet objekt per ruta, och kasta ut rutor som lämnat vyn, är det som håller ett stort lager inom webbläsarens budget.

Påverkar koordinatsystemet prestandan?#

Indirekt, och det spelar större roll för riktigheten. Förenklingstoleranser och täthetsceller behöver likformiga meter, så data som lagras i ett projicerat koordinatsystem i meter går att generalisera medan data i grader inte gör det. Dessutom: matchar kartans projektion lagrets kan rutorna byggas direkt i den projektionen, helt utan omräkning i klienten — mer om det i vår guide till nordiska koordinatsystem.

Testa med ditt tyngsta lager#

Det ärliga provet på ett webbGIS är aldrig demodatan — det är lagret som har gjort dig besviken förut. Ta det största du har, ladda upp det i OrbGIS och zooma ut hela vägen. Allt ovanstående sker på vägen in, utan en enda inställning att fylla i. Har du en särskilt besvärlig datamängd är du välkommen att höra av dig — vi gillar de svåra.

← Alla artiklar

Få nya artiklar direkt till din inkorg

Vi hör av oss när vi publicerar något nytt — inget annat.