Een prototype maken van een vloeiendere kaart

Een kijkje in hoe Google Maps werkt

Toen ik aan Google Maps werkte als UX Engineer, was een van de dingen die ik echt wilde kunnen doen, prototypen maken die animaties met zoom konden synchroniseren. De JavaScript Maps API heeft echter beperkte controle over de zoom, dus om meer controle te hebben, heb ik geëxperimenteerd met het gebruik van HTML5 canvas om de tegels samen te stellen in een volledig aangepaste implementatie.

Om te begrijpen wat een soepele zoomfunctie uitdagend maakt voor moderne kaarttoepassingen, helpt het om eerst te begrijpen hoe de kaart is gemaakt en hoe Google Maps (en de meeste andere) kaartclients werken - u kunt zoeken naar de vetgedrukte titels om daar direct naartoe te gaan.

  1. De aarde plat maken - het proces waarbij een 3D-wereldbol wordt omgezet in een 2D-kaart.
  2. Google Maps - hoe Google de wereld van kaarten in 2005 veranderde, en hoe de klant weergeeft.
  3. De gerasterde kaart animeren - de aangepaste aanpak die ik heb gebruikt om vloeiende animaties mogelijk te maken.

1. De aarde plat maken

Ik hoop dat het voor niemand een verrassing zal zijn, maar de wereld is rond - hoewel merkwaardig genoeg is het niet echt een bol, maar in het midden een beetje geknikt en breder.

Het proces van het nemen van dit meestal bolvormige object en het weergeven in 2D is iets waar cartografen al duizenden jaren mee worstelen en ruzie maken. Er is eigenlijk geen "beste" manier om dit te doen, elk komt met afwegingen.

Breedtegraad en lengtegraad

Als je de wereld voorstelt als een bol die om zijn as draait, staat de noordpool helemaal bovenaan, de zuidpool helemaal onderaan en de evenaar is de denkbeeldige lijn die rond het midden loopt.

streepplaat

Als je de evenaar beschouwt als een cirkel die horizontaal zit (zoals een riem), dan kun je je voorstellen dat er extra horizontale cirkels boven en onder zijn, elk van deze evenwijdig aan de evenaar. Na een cirkel naar rechts ga je recht naar het oosten en volg je een cirkel naar links door het westen. Deze denkbeeldige lijnen worden breedtecirkels genoemd. De breedtegraden zijn altijd op dezelfde afstand van elkaar, ongeacht hoe ver u naar het oosten of westen reist.

Omdat de aarde om zijn as is gekanteld, zit de zon niet altijd direct boven de evenaar, maar lijkt hij tijdens onze baan enigszins naar het noorden en het zuiden te dwalen. De Kreeftskeerkring is de meest noordelijke breedtecirkel waarop de zon direct boven (op de zonnewende van juni) kan verschijnen, en de Steenbokskeerkring is het equivalent op het zuidelijk halfrond (en de zonnewende van december). Dat is eigenlijk niet belangrijk voor Google Maps, maar het is leuke trivia.

Breedtegraad geeft aan hoe ver je naar het noorden of zuiden bent (want hoe ver je ook naar het oosten of westen gaat, je bent helemaal niet naar het noorden of zuiden gegaan).

In de andere richting, loodrecht op de evenaar, lopen de meridianen of lengtegraden - elk verbindt de noordpool in een rechte lijn met het zuiden. Volg een lijn omhoog en ga naar het noorden, naar beneden naar het zuiden.

Lengtegraad geeft aan hoe ver oost of west je bent (want ongeacht hoe noord of zuid je gaat, je bent helemaal niet naar het oosten of westen verplaatst).

Het cruciale verschil tussen lengte- en breedtegraad is dat terwijl breedtegraad altijd op een gelijke afstand op een bol is, de lengtegraden het verst uit elkaar liggen in het midden (de evenaar) en dichter bij elkaar komen in de buurt van de polen (waar ze elkaar raken).

Breedtegraad en lengtegraad meten

Zowel lengte- als breedtegraad worden gemeten in graden. Beginnend vanuit het midden van de bol meet je gewoon de hoek op elke cirkel (de parallelle breedtecirkels en de loodrechte cirkels van lengtegraad).

Breedtegraad is de hoek gevormd door de evenaar, dus is het 0 ° op de evenaar en tot 90 ° noord of zuid (op de polen). Halverwege de Noordpool zou op 45º N zijn.

Lengtegraad is de hoek gemeten vanaf de Prime Meridian (een willekeurige lijn die door Greenwich, Engeland loopt). Het is 0º in Greenwich en tot 180º oost of west (de andere kant van de wereld).

Wereldwijde positionering

Een laatste stukje trivia, dat maakt niet echt uit voor Google Maps (maar ik vind het toch interessant), is hoe mensen zich vóór GPS bevonden.

Breedtegraad is relatief eenvoudig, u hoeft alleen de hoek tussen de horizon en een bekende ster (of onze zon) te meten en vervolgens een beetje wiskunde te maken. Mensen navigeren al duizenden jaren op basis van de sterren, Polaris (de Noordster) is vooral populair - op de Noordpool ligt Polaris direct boven het hoofd (op 90 ° tot de horizon). Op de evenaar lijkt Polaris aan de horizon te zitten (op 0º). Tussen deze twee uitersten is de hoek met Polaris de breedtegraad Noord. Je kunt ook andere sterren gebruiken, ze vereisen gewoon meer wiskunde.

Lengtegraad is veel, veel, moeilijker - in feite zo moeilijk dat niemand het tot het einde van de 18e eeuw goed kon doen (noch gemakkelijk tot veel later). De oplossing is tijdrovend - de aarde draait elke 24 uur met dezelfde snelheid, 360º (of 15º per uur), dus als u de tijd weet waar u bent begonnen en de tijd waar u zich bevindt, kunt u de afstand berekenen op basis van het verschil. Makkelijk om vandaag te doen, met nauwkeurige klokken, maar eerder moeilijk om te doen.

Oorspronkelijk gebruikten ze de voorspelde posities van hemellichamen op bekende tijden om te berekenen wat de tijd was, en vergeleken dat vervolgens met de lokale tijd (met behulp van de zon 's middags om de' lokale klok 'te resetten). De meest gebruikte set voorspellingen was de Nautical Almanac gepubliceerd door de Royal Observatory in Greenwich, Engeland - die, als je je ooit hebt afgevraagd waarom het GMT (Greenwich Mean Time) heette, of waarom de Prime Meridian door Greenwich loopt, het is want voor de langste tijd was iedereen aan het meten ten opzichte van het observatorium in Greenwich.

De grootste doorbraak in het meten van lengtegraad was meer nauwkeurige klokken, zodat ze het sterrenkijken konden stoppen en gewoon GMT konden controleren - lengtegraad was het aantal uren vermenigvuldigd met 15º. In feite is dit zo ongeveer hoe iedereen de lengtegraad heeft berekend tot de uitvinding van het Global Positioning System (GPS), dat slechts een set supernauwkeurige atoomklokken in de ruimte is (en een aantal indrukwekkende wiskunde).

Als je hier half zo gefascineerd door bent als ik, zou ik je echt aanraden dit geweldige boek erover te lezen.

Een projectie kiezen

De methode voor het vertalen van de punten op een 3D-wereldbol naar een 2D-vlak wordt een kaartprojectie genoemd. Er zijn veel verschillende projecties, elk met hun eigen sterke punten en beperkingen, en geen zonder enige vorm van vervorming van de werkelijke geometrie - hier zijn er maar een paar:

Projecties op Wikipedia

De door Google Maps geselecteerde projectie is een aangepaste versie van de Mercator-projectie, creatief getiteld Web Mercator [het belangrijkste verschil is dat de wereld ervan uitgaat dat de wereld een bol is, in plaats van een afgeplatte ellipsoïde].

Er zijn een paar redenen om Mercator te kiezen, maar de beste reden is dat noord en zuid recht omhoog en omlaag gaan, en oost en west recht links en rechts zijn - op sommige van de andere projecties zouden deze lijnen krommen of afwijken als u zich verplaatst de kaart. Dit komt sterk overeen met de reden waarom Mercator zo wijdverspreid was voor navigatie - lijnen met een constante koers (dwz als u een kompasrichting kiest en zich eraan houdt) zijn volledig recht. Een bijkomend voordeel is dat je, omdat het een cilindrische projectie is, je horizontaal kunt wikkelen en de kaart kunt betegelen.

Enkele van de andere speciale eigenschappen van de Mercator-projectie zijn dat de schaal in elke richting rond elk gelokaliseerd punt hetzelfde is (dwz als u op een stad inzoomt, zijn de afstanden noord en zuid hetzelfde als oost en west) en zijn alle hoeken nauwkeurig weergegeven (dwz het oosten is 90º van het noorden).

De grootste kritiek op de Mercator-projectie is dat het de schaal van landen aanzienlijk verstoort naarmate je verder van de evenaar komt (bijv. Groenland ziet er zo groot uit als Afrika, ondanks dat het minder dan een tiende van de omvang is).

Bedenk wat er gebeurt als je een sinaasappel pelt en stel je dan voor dat je hetzelfde met de aarde doet. Als je langs de reeks longitudinale lijnen zou snijden, zou je eindigen met een assortiment ingeklemde plakjes - op de wereldbol lijkt elke verticale lijn recht, maar als je het in twee dimensies uitpakt, wordt het gebogen (onthoud hoe die lengtegraden dichter bij elkaar op de polen?).

De wereld uitpakken

Om de Mercator-kaart weer samen te voegen, moeten we die segmenten horizontaal uitrekken. Op de evenaar raken ze al, dus ze hoeven helemaal niet te strekken, maar aan de polen is er een heel grote kloof en ze moeten veel strekken (technisch oneindig). Om de afstanden en hoeken in beide richtingen te behouden terwijl deze horizontaal rekt, wordt deze verticaal met dezelfde hoeveelheid uitgerekt. Hoe dichter het bij de polen komt, hoe meer het moet rekken.

Dit is een beetje gemakkelijker te visualiseren als u probeert een cirkel van dezelfde grootte op verschillende punten op de kaart te tekenen. Voor de Mercator-projectie kun je zien dat de cirkels veel groter lijken in de richting van de polen (hoewel ze in werkelijkheid exact dezelfde grootte hebben). Dit kwam omdat we de kaart meer bij de polen moesten strekken om hem te verbinden.

Vervorming van schaal

Dit is echt alleen van belang wanneer er erg wordt uitgezoomd, omdat de schaal in elke gelokaliseerde regio hetzelfde is, dus voor een bepaalde stad of zelfs een land blijft alles evenredig. En het is eigenlijk alleen een belangrijk probleem voor de polen, maar omdat pinguïns en ijsberen geen Google Maps gebruiken, is er niet veel klacht.

Als we terugdenken aan de bol - waar breedtegraden parallel aan elkaar waren, en altijd dezelfde afstand uit elkaar, terwijl lengtegraden dichter bij elkaar aan de polen kwamen - met Mercator blijft de breedtegraad perfect parallel en wordt de lengte perfect loodrecht. Alles is recht.

Deze fantastische video van Grafonaut toont de hele transformatie:

2. Google Maps

In 2005 is Google Maps geïntroduceerd met behulp van een innovatie die nog steeds ten grondslag ligt aan elke afzonderlijke kaartservice - de tegelkaart. In 2013 was er een belangrijke update om WebGL te gebruiken en rendering aan de clientzijde toe te voegen, maar de betegelde aanpak blijft bestaan.

Ik merk op dat Google Maps het concept van de tegelkaart niet heeft uitgevonden, maar het was misschien wel de eerste reguliere toepassing die het gebruikte, en de combinatie met AJAX en het internet heeft de aanpak zeker populair gemaakt.

Betegelde kaarten

In plaats van te proberen een enkele afbeelding weer te geven, splitst Google de kaart op in kleinere tegels en plaatst deze vervolgens naast elkaar om een ​​grotere foto te maken - net als een mozaïek.

Betegelde kaart

De belangrijkste reden hiervoor is de afbeeldingsgrootte. Op het hoogste zoomniveau van Google Maps zou het beeld meer dan 500 miljoen vierkante pixels zijn (het dubbele van dat op HDPI-schermen), dat is meer dan 25.000 terabytes (denk ik?), Zelfs met royaal optimistische beeldcompressie. Ervan uitgaande dat uw browser die afbeelding zou kunnen weergeven, zou het meer dan 6 jaar duren om te downloaden met Google Fiber.

De tweede reden is de serverbelasting. In plaats van tegels te gebruiken, kan de server voor elke gebruiker een kaart van perfect formaat genereren, op exact zoomniveau, lengte- en breedtegraad en de juiste grootte om hun venster te vullen. Maar dat zou waarschijnlijk betekenen dat elke gebruiker een volledig aangepaste kaart nodig heeft en met meer dan 1 miljard maandelijkse gebruikers zijn dat veel aangepaste kaarten! Het zou ook betekenen dat je elke keer dat je de kaart poogt, zelfs een paar pixels nodig hebt om een ​​volledig nieuwe kaart te downloaden.

Het leuke van tegels is dat iedereen ze kan delen, de server ze kan opslaan in de cache (en ze zelfs vooraf kan genereren) en de client ze gemakkelijk kan verplaatsen. Sommige gebruikers kunnen een paar extra tegels downloaden als hun venster groter is, maar de tegels moeten nog steeds slechts eenmaal worden weergegeven.

Opmerking: voor de 2013 vectorgebaseerde WebGL-implementatie verzendt de server in plaats van afbeeldingstegels de vectorinformatie (elk pad en polygoon) en geeft de client de afbeeldingen weer. Deze vectorinformatie is echter nog steeds gegroepeerd in "tegels" - om exact dezelfde redenen (maakt servercaching mogelijk en biedt de client een schone manier om gegevensverzoeken onder te verdelen). Terwijl de volgende secties de rasterimplementatie bespreken (die nog steeds wordt gebruikt door de Maps JavaScript API), is de algemene aanpak ook van toepassing op de WebGL-versie.

Zoomniveaus

Google Maps heeft een variërend aantal zoomniveaus op basis van de locatie, maar het is meestal ongeveer 21. Op het meest uitgezoomde (niveau 0) wordt de hele kaart weergegeven door een enkele vierkante tegel van 256 bij 256 pixels. Bij elk incrementeel zoomniveau verdubbelt de kaart in grootte in elke richting - elke tegel wordt vervangen door 4 meer gedetailleerde (2x2) tijdens het zoomen. Elke tegel is nog steeds slechts 256 bij 256 pixels en wanneer u ze combineert, krijgt u dezelfde kaart (alleen meer gedetailleerd).

Op zoomniveau 0 is de wereldkaart een enkele tegel, bij zoom 1 is de kaart 2 tegels in elke richting, bij zoom 2 is het 4 tegels breed, bij zoom 3 is het 8 tegels breed, enzovoort (telkens verdubbelend) . Dus terwijl de totale breedte en hoogte elk niveau verdubbelt, gaat het gebied sneller omhoog (1 tegel, 4 tegels, 16 tegels, 64 tegels, enz ...). Tegen de tijd dat het zoomniveau 21 bereikt, is de kaart 2 miljoen tegels breed en bevat deze in totaal meer dan 4 biljoen tegels.

Kaarttegels op elk zoomniveau

Elk zoomniveau krijgt zijn eigen stijlregels om te beslissen welke informatie moet worden weergegeven. Er is weinig waarde om weginformatie aan de wereldkaart toe te voegen, noch om informatie aan de landkaart op te bouwen, enz ... Er is een zeer hardwerkend team dat constant de labels en functies combineert die op elk niveau worden gepresenteerd en gestileerd.

Als algemene vuistregel zijn de eerste paar niveaus vrijwel alleen de wereldkaart. Bij zoom 5 zijn de continenten en landmassa's de primaire kenmerken. Op niveau 10 verschijnen de stadsdetails. Op niveau 15 zijn de straten duidelijk zichtbaar. En met zoom 20 worden alle gebouwen weergegeven.

We kunnen de pixelschaal voor deze zoomlenzen gemakkelijk schatten - we zouden het precies kunnen doen met complexere wiskunde - maar op de evenaar (waar de Mercator-projectie de kaart niet uitrekt) is het een eenvoudige berekening:

Bij zoom 1 vertegenwoordigt elke pixel 78 km (48 mijl), zoom 5 is 5 km (3 mijl), zoom 10 is 150 m (164 yards), 15 is 5 m (5,5 yards), en bij zoom 20 is elke pixel het equivalent van 15 cm ( 6 inch) - dat is indrukwekkend gedetailleerd!

positionering

Google Maps heeft naast de breedte- en lengtegraad drie concepten van coördinaten: wereldcoördinaten, pixelcoördinaten en tegelcoördinaten.

Wereldcoördinaten zijn onafhankelijk van het zoomniveau en worden gebruikt om te vertalen tussen breedtegraden en lengtegraden en de huidige positie op de kaart (of vice versa). Ze worden berekend op basis van een enkele tegel op zoomniveau 0. De lengte- en breedtegraad wordt toegewezen aan de fractionele x- en y-pixel op die enkele tegel (een getal tussen 0 en 256 - de breedte en hoogte).

De conversie is heel eenvoudig, hoewel ik niet kan beweren dat ik de wiskunde begrijp. Het in kaart brengen van de lengtegraad is gemakkelijk te begrijpen omdat het direct wordt vertaald, maar de breedtegraad is gecompliceerder vanwege de scheefstand wanneer deze de polen nadert.

Pixelcoördinaten verwijzen naar de exacte pixelpositie van een lengte- en breedtegraad op een opgegeven zoomniveau. Ze kunnen worden berekend door de wereldcoördinaten te nemen en te vermenigvuldigen met de totale schaalwaarde voor het zoomniveau - wat gemakkelijk te berekenen is omdat de schaal elke zoom verdubbelt.

Tegelcoördinaten zijn hoe de client de server om afbeeldingen vraagt.

Tegelcoördinaten

Ze zijn gepositioneerd in rijen en kolommen, met rij 0 kolom 0 linksboven, rijen oplopend naar rechts en kolommen terwijl u naar beneden gaat. Net als pixelcoördinaten zijn tegels afhankelijk van het zoomniveau, in feite is het een eenvoudige afbeelding van pixel tot tegel door de pixelcoördinaten te delen door de tegelgrootte en het integrale getal te nemen.

De client kan eenvoudig berekenen welke tegels hij nodig heeft door de tegelcoördinaten voor elke hoek van het scherm te berekenen. Meestal voegt het een beetje opvulling toe, als een manier om vooraf te laden, voor het geval de gebruiker de kaart enkele pixels draait.

Cliënten kunnen eenvoudig tegel-URL's genereren met behulp van deze coördinaten - bijvoorbeeld zoomniveau 1, rij 0, kolom 0, is deze tegel die Noord-Amerika bevat. Het is triviaal om code in te bouwen, hoewel de Maps API het voor u afhandelt (samen met stijl en andere voordelen).

Pannen

Interactie met de kaart is waar de tegels echt hun waarde bewijzen. Vóór Google Maps werkten de dingen een beetje meer als een Atlas - als je aan de rand van de kaart kwam, moest je de pagina omslaan om iets meer te zien. Het mooie van tegels is dat het gebruikers in staat stelde om zonder onderbreking de kaart te verkennen terwijl ze rondzochten en inzoomden.

Elke tegel wordt absoluut in een container geplaatst en wanneer u de kaart verschuift in plaats van elke tegel te verplaatsen, hoeft alleen de container te worden verplaatst (en de tegels verschuiven ermee). Hierdoor kan de client het aantal DOM-wijzigingen minimaliseren.

De container beweegt, niet de tegels

Het is gemakkelijk om de positie van elke tegel te berekenen door de tegelcoördinaat eenvoudig te vermenigvuldigen met de tegelgrootte.

De laatste positioneringstrick is het minimaliseren van het totale aantal tegels dat de browser moet weergeven.

Een constante DOM handhaven

Terwijl de gebruiker de kaart draait, controleert de client welke tegels zichtbaar moeten zijn en laadt de nieuwe of verwijdert de tegels die niet meer zichtbaar zijn.

Dit wordt zo snel gedaan dat de gebruiker het zelden opmerkt (en in plaats van te knippen tot de harde grenzen van het scherm, haalt het vaak een paar extra tegels aan beide kanten op als buffer). [Deze aanpak is eigenlijk niet zo verschillend van hoe Google Foto's nu werkt]

Zooming

Panning rond is naadloos, maar zoomen is een van de uitdagingsgebieden met een betegelde kaart.

Aan de technische kant is de uitdaging minder hoe de tegels te plaatsen, maar meer hoe te schakelen tussen niveaus. Elk zoomniveau is een schaalverdubbeling en er zijn geen tussenliggende tegels om te helpen.

Tussen niveaus schakelen

Zoomen was oorspronkelijk heel eenvoudig, het verving gewoon de kaart door de volgende reeks tegels, maar dat was een beetje schokkend omdat het plotseling tussen niveaus zou "klikken".

Een manier waarop dit wordt gecompenseerd, is in plaats van in te zoomen op het midden van de kaart, het houdt elke locatie onder de cursor stil (vastgemaakt onder de cursor). Dit stelt gebruikers in staat om letterlijk te "richten" naar de functie waarin ze zijn geïnteresseerd en waarin ze zich kunnen concentreren (ze bepalen het referentiepunt).

Snelle schaalanimatie (Maps JavaScript API)

Bij een recentere aanpassing voelde het sneller aan. Tijdens het zoomen behoudt het tijdelijk beide zoomniveaus ter waarde van tegels (de oude en de nieuwe) en voert een zeer snelle schaalanimatie tussen hen uit - de nieuwe tegels beginnen met de helft te worden verkleind en de oude tegels worden tot dubbele grootte geanimeerd.

Het 'snapt' nog steeds tussen lagen wanneer de animatie is voltooid, maar het gebeurt allemaal zo snel dat het oog soort van de tussenliggende status verbeeldt.

Deze scale & snap-aanpak wordt nog steeds gebruikt door de Google Maps JavaScript API, Bing Maps, Here Maps, Yahoo Maps, MapQuest en OpenStreetMap (LeafletJS).

Schaalovergang tijdens zoom

Mogelijk is de grootste beperking met scale & snap dat de gebruiker geen controle heeft over de animatie, zodra ze de zoom activeren, wordt deze uitgevoerd totdat deze is voltooid, is er geen mogelijkheid om de snelheid te regelen of te pauzeren in een tussenstand.

Vector kaarten

In 2013 bracht Google Maps een belangrijke update uit voor maps.google.com die het gebruik van PNG-tegels voor afbeeldingen stopte en begon met het downloaden van vectortegels. Naar deze vectortegels wordt nog steeds verwezen door hun tegelcoördinaten, gedragen zich nog steeds op dezelfde manier als de rastertegels, maar in plaats van een afbeelding, bevatten ze alle labels, paden en polygonen - en worden ze op de client getekend.

Er zijn verschillende redenen waarom dit belangrijk is - de vectorgegevens comprimeren beter dan afbeeldingen (bespaart dus bandbreedte), het maakt dynamische updates en styling mogelijk (bijvoorbeeld als een gebruiker op een doorvoerroute klikt), en het maakt aanzienlijk verbeterde zoomfunctie mogelijk .

Soepel zoomen

Met de gerasterde PNG's kan de kaart tijdens het schalen niet vertellen wat een weg is (en moet dezelfde breedte worden getekend) of wat een park is (en moet groter schalen), wat betekent dat alles gewoon wordt uitgerekt en gepixeld. Met de vectorinformatie kan de klant labels correct gepositioneerd houden, de wegbreedtes handhaven en alle polygonen schalen - het resultaat is een ongelooflijk soepele zoomervaring. Nog beter, het is volledig responsief, zodat de gebruiker de snelheid kan regelen en zelfs bij fractionele zoomniveaus kan stoppen.

Als je echter goed kijkt (of zelf gaat proberen) terwijl het de tegels heel soepel schaalt, is er geen nieuwe informatie totdat je stopt. Zodra de gebruiker het zoomen pauzeert, laadt de kaart snel de vectortegels op het nieuwe zoomniveau en verwisselt ze. Het is echt soepel en snel.

MapBox is een van de andere clients die vectortegels op het web gebruiken en ook deze soepele & snelle aanpak gebruiken, hoewel ze agressiever nieuwe tegels laden tijdens de zoomovergangen.

3. Animeren van de gerasterde kaart

Om de kaart vloeiend te kunnen animeren, is het meest kritieke element de mogelijkheid om fractionele zoomniveaus in te stellen (bijv. Halverwege tussen twee van de integrale zoomniveaus die de tegels weergeven). Met vectortegels kan dit op de client worden aangepast, maar met de rastertegels moeten we creatiever worden.

Om dit te ondersteunen, schreef ik een volledig aangepaste versie van de Maps JavaScript API, die de afbeeldingstegels van de Maps Server hergebruikt, maar ze vervolgens positioneert en de interactie zelf afhandelt. Dit maakte volledige controle over de schaal en positionering van elke tegel mogelijk, evenals controle over de zoom en animatie. Alles verteld was het 4.442 regels commentaarcode - het is opmerkelijk hoe weinig code de klant nodig heeft, maar als je erover nadenkt, wordt het meeste van het echt harde werk gedaan op de server (uitzoeken welke wegen, meren, gebouwen, enz ... zijn zichtbaar in elke tegel, bepalen de stijlen en kleuren en geven deze vervolgens weer als afbeeldingen).

De rest van dit artikel verwijst naar mijn prototype-code en niet naar de normale versie van Google Maps.

Fractionele zoomniveaus maken

Net als bij Google Photos om de dekking tussen afbeeldingen met een lage resolutie en een hoge resolutie te vervagen om de details bij het laden te laten overvloeien, was mijn theorie dat we de tegels van verschillende zoomniveaus konden mengen om een ​​tussenstatus te creëren.

We zouden lagere zoomniveaus kunnen nemen en ze kunnen vergroten zoals bij het inzoomen, of het tegenovergestelde doen en de hogere zoomniveaus verlagen bij het uitzoomen. Door tegels van meerdere zoomniveaus te schalen en te overlappen, kunnen we soepel overstappen tussen integrale zoomlenzen en dit op een wiskundig voorspelbare manier doen (perfect voor het synchroniseren van animaties).

Dit is mogelijk omdat Google de Mercator-projectie gebruikt - de schaal is uniform voor gelokaliseerde regio's, dus door de tegels lineair te schalen, blijven de vormen behouden.

Het berekenen van de dekking voor de cross-fade is eenvoudig en lineair. Bij de overgang tussen twee zoomniveaus moet de volgende tegel bij de eerste zoomstap volledig transparant (dekking 0) zijn en bij de volgende volledig dekkend (dekking 1). De dekking is dus slechts 1 min de afstand waarop de tegelzoom zich bevindt van de kaartzoom (hoewel deze in de praktijk klemt naar waarden tussen 0 en 1).

Schaal is ook niet zo ingewikkeld. Gezien de schaal verdubbelt op elk zoomniveau, is het mogelijk om het te schalen bedrag voor een tussenniveau te berekenen met machten van 2.

Als de mapZoom een ​​volledig niveau hoger is dan de tegel (mapZoom - tileZoom = 1), dan zou de wiskunde 2¹ of 2 opleveren. Als de mapZoom hetzelfde niveau is als de tegel (mapZoom - tileZoom = 0), zou de wiskunde 2⁰ zijn of 1. Het mooie van het berekenen van machten is dat het werkt voor breuken en negatieven. Als de mapZoom een ​​volledig niveau lager is dan de tegel (mapZoom - tileZoom = -1) krijg je 2⁻¹ of 0,5. Halverwege tussen zoomniveaus (mapZoom - tileZoom = 0.5) krijg je 2 ^ (0.5) of 1.414, en je kunt dit doen om soepel tussen elke status te schalen.

De volgende afbeelding laat zien hoe dit van toepassing is tijdens het inzoomen. De monochrome tegel is het startzoomniveau en de gekleurde tegels zijn het volgende zoomniveau (getekend in een dambord zodat u het verschil kunt waarnemen). Je kunt zien dat hoe dichter we bij het volgende zoomniveau komen, hoe meer de gekleurde tegels domineren en het detail (bijv. Labels) begint te vervagen.

Inzoomen

Hier is de tegenovergestelde richting, uitzoomen. Monochroom is opnieuw het startzoomniveau en kleurde het vorige zoomniveau - in dit geval verliezen we detail terwijl we uitzoomen.

Uitzoomen12 fps

Het werkt heel goed, biedt een relatief soepele zoom (veel beter dan schaal en snap) en hoewel niet zo soepel als de vectorweergave (smooth & snap), elimineert het eigenlijk het snap-aspect volledig.

Ik heb de foutopsporingsoverlay in de GIF ingeschakeld, zodat je kunt zien waar de nieuwe tegels worden geladen. Het is in de praktijk soepeler (60 frames per seconde) maar ik heb de GIF beperkt tot 12 fps. Hier is de video. Als alternatief is dit hier zonder foutopsporingsregels.

Aan beide uiteinden is de kaart bijna onmerkbaar anders dan de integrale zoom, hoewel deze in het midden in een ongemakkelijke staat kan komen met labels die met elkaar concurreren. Dit zou geen geweldig effect zijn als u op een fractioneel zoomniveau blijft, maar het is geen probleem tijdens overgangen - en om fractionele zooms te voorkomen, is het voor de klant gemakkelijk om terug te gaan naar een integrale zoomlens wanneer de gebruiker klaar is met zoomen, wachten totdat ze loslaten, zodat ze ondertussen volledige controle behouden.

Zoomen tussen integrale niveaus 3 en 4

Zullen we dit effect scale & blend noemen? Het zoomt niet alleen soepel, maar reageert ook altijd op gebruikersinvoer.

Zoom animeren met 24 fps

HTML5 Canvas

Om de beste prestaties te bereiken, heb ik de normale webbenadering van het gebruik van standaard HTML-elementen opgegeven. Zoals beschreven in de vorige sectie, gebruikt de Maps API een combinatie van DIV- en IMG-elementen om de kaart weer te geven. Elke afbeelding wordt in een bovenliggende container geplaatst en die container wordt verplaatst terwijl de kaart draait. Voor normale webtoepassingen heeft de browser dingen laten beheren veel voordelen, de browser neemt alle beslissingen over wanneer het scherm opnieuw moet worden getekend, hoe elementen moeten worden geplaatst en geplaatst, evenals het stroomlijnen van interactie (zoals scrollen en klikgebeurtenissen). De browser hiervoor gebruiken is bijna altijd de beste aanpak.

Soms is het echter - vooral voor zeer aangepaste tekeningen - voordelig om dit handmatig te doen. Om dit te ondersteunen, hebben browsers het canvas-element gemaakt. In tegenstelling tot normale HTML, waar u elementen maakt en deze aan de pagina toevoegt, voert u met een canvas tekenopdrachten uit. U kunt lijnen, bogen, cirkels, rechthoeken en zelfs complexe curven tekenen. Het canvas doet niets extra voor u en behandelt het resultaat net als een foto. Als u een afbeelding in het canvas tekent, moet u deze precies vertellen waar en welke grootte, en als u besluit die afbeelding enkele pixels te verplaatsen, moet u het hele canvas wissen en alles erop overschilderen (inclusief de andere lijnen en afbeeldingen). Als u door een normale website bladert, worden de posities opnieuw berekend en opnieuw getekend. Als u gebruikers een canvas wilt laten scrollen, moet u elke positie handmatig opnieuw berekenen en vervolgens elk stuk zelf opnieuw tekenen.

Als het klinkt als veel werk, is het dat ook echt, en dat is een van de vele redenen waarom canvas niet vaker wordt gebruikt; maar er zijn bepaalde situaties waarin een canvas de beste manier is om iets te bereiken, en een volledig aangepaste kaartweergave is er een van (omdat het schalen en positioneren van elke tegel toch aangepast was, heeft het gebruik van een canvas wat complexiteit toegevoegd maar de overhead verminderd) .

Een manier om het verschil in de manier waarop de pagina is opgebouwd te visualiseren, is door naar de resulterende HTML te kijken. De normale aanpak creëert tientallen elementen en voegt ze toe aan de pagina, maar voor de canvasbenadering is er heel weinig, alles was een tekenopdracht binnen het canvas.

Verschil in paginastructuur tussen de op elementen gebaseerde en de canvasbenadering

Fallback-tegels

Een randgeval om te verwerken bij het schalen en mengen van de tegels is wat er gebeurt als er geen tegels zijn om te mengen. Als de onderste zoomlens nog niet is geladen, is het beter om in plaats van de dekking op het volgende niveau te animeren, deze beter volledig dekkend te starten - anders zullen er fragmentarische gaten in de kaart zijn.

Het berekenen van de dekking van fallback-tegels is een gebied dat de aangepaste canvassamenstelling echt laat zien - elke tekenbewerking kan aangepaste dekking instellen, en omdat canvas echt efficiënt is voor kleine tekenbewerkingen (bijvoorbeeld de grootte van een kaarttegel), kunnen we aangepaste aangepaste dekkingsniveaus instellen voor elke individuele tegel. Als we afhankelijk waren van de browser om dit met aangepaste elementen te doen, zouden alle individuele animaties vertraging kunnen veroorzaken.

De client tekent altijd fallback-tegels (de onderliggende tegels) bij volledige dekking en wijzigt alleen de dekking van de primaire tegels (doelzoomniveau) waarop deze bovenop ligt. Vóór de verfcyclus controleert het of de primaire tegel volledige fallback-dekking heeft - dat wil zeggen of alles onder die tegel is getekend.

Als we hier bijvoorbeeld inzoomen, van zoom 0 tot 1, hebben die gekleurde tegels een volledige dekking en die ene grijze tegel eronder overlapt volledig. Dit betekent dat ze hun dekking veilig kunnen laten animeren zonder gaten te laten.

Het tegenovergestelde is echter niet waar. Als we uitzoomen, van 1 tot 0 (dus de gekleurde tegels zouden de onderlaag zijn), kunnen we de dekking op de grijze tegel niet animeren omdat deze enkele gaten zou laten.

Een mogelijke oplossing hiervoor zou zijn om de primaire tegels eenmaal zonder dekking als een volledig ondoorzichtige basislaag te schilderen, vervolgens de fallback-tegels te schilderen en vervolgens de primaire dekking van de tegel veilig te wijzigen (in het slechtste geval zou het overgaan met zichzelf) - maar dat zou toenemen het aantal tekenbewerkingen voor weinig merkbare verbetering.

Een ander ding dat we kunnen doen met de dekking is om snel te animeren in nieuw geladen tegels, dus in plaats van op zijn plaats te klikken, is er een snelle vervaging.

Zoomrichting en snelheid

De client houdt ook bij in welke richting gebruikers zoomen (in of uit), en hoe snel ze zoomen, en gebruikt dit om te bepalen of nieuwe tegels moeten worden geladen.

Als u bijvoorbeeld inzoomt van 14 tot 15, zal de client niet de moeite nemen om meer tegels te laden vanaf zoomniveau 14, maar in plaats daarvan prioriteit geven aan het ophalen van de nieuwe die het nodig heeft vanaf 15. Als het tegenovergestelde gebeurt, uitzoomen vanaf 15 tot 14, zou de client alleen proberen de nieuwe tegels van 14 te laden.

Het gebruikt de zoomsnelheid om te beslissen of het zin heeft om tegels te laden, of dat het waarschijnlijk is dat de laag voorbij wordt ingezoomd voordat de afbeeldingen kunnen worden geladen. Bij snel zoomen kan de gebruiker bijvoorbeeld in slechts een seconde helemaal van zoom 4 naar 15 racen - en het heeft weinig zin om 5, 6, 7, 8 te laden ... omdat het gewoon een verspilling van tegels zou zijn. Gelukkig kunnen we voor inzoomen slechts een paar tegels schalen (onthoud bij zoom 0 dat de tegel de hele wereld vertegenwoordigt) om de kleuren / vormen vaag representatief te houden.

Schalen voorkomen

Gezien de kaart steeds gedetailleerder wordt bij hogere zoomniveaus, ging ik naïef aan dat het beter zou zijn om de hogere niveaus te gebruiken en deze te verkleinen bij het uitzoomen.

Te veel tegels verkleinen

In de praktijk werkte dit echter niet zo goed - terwijl de schaal van alle polygonen (bijv. Water en parken) goed bewaard is gebleven, worden alle labels en pictogrammen ook geschaald, waardoor een kaart er echt rommelig uitziet, vooral in het midden. De uitvoering kreeg ook een enorme hit, want in plaats van enkele tientallen tegels te tekenen, waren er ineens honderden beschikbaar om te tekenen.

Om dit te voorkomen, heb ik tegels geschaald met meer dan 1 zoomverschil. Dat wil zeggen bij het inzoomen op de fractionele niveaus van de 14s (bijv. 14.2) kan het tegels van zoom 15 gebruiken, maar nooit, ooit tegels van 16 of hoger.

animaties

Mijn verklaarde intentie bij de start van het project was om animaties met zoom programmatisch te kunnen synchroniseren, hoewel het ook het bijkomende voordeel heeft dat het zich gewoon beter voelt - het is vloeiender en reageert sneller op gebruikersinvoer. Bij integrale zoomniveaus gedraagt ​​het zich hetzelfde als de normale implementatie. Ik denk echter dat het vrij effectief is om te animeren - je kunt het zelf beoordelen.

Hier is een voorbeeld van een mogelijke vliegbaan van San Francisco naar Australië. [alternatief de volledige video, of één met de foutopsporing-overlay]

Vliegen (en soepel animeren) van San Francisco naar Australië

Natuurlijk is er geen reden om het te beperken tot zoomanimaties. Een bijkomend voordeel van alles op een doek te schilderen, is dat het ons echt creatieve dingen kan laten doen als we dat willen.

destination-out globalCompositeOperation

Getoond wordt een voorbeeld dat de bestemmingscompositiemodus op canvas gebruikt om vormen uit de kaart te 'knippen' - hier ligt een monochrome kaart bovenop een gekleurde kaart en wordt de onthulling geanimeerd. [volledige video]

Toekomstig gebruik

Zo leuk als het was om te schrijven, en zo nuttig als dit was voor mijn prototyping (het heeft tientallen prototypes aangedreven tijdens mijn tijd in het team) Google en MapBox hebben beide een vectorweergave die deze schaalaanpassing overtreft. Ik moedig alle anderen, die niet upgraden naar vectortegels, graag aan om te overwegen iets soortgelijks te implementeren. Het is een vrij eenvoudige techniek die niet veel complexiteit toevoegt aan de client (zeker minder werk dan vectorweergave), maar een veel soepelere en responsievere ervaring mogelijk maakt.