Prototype et glattere kort

Et glimt af, hvordan Google Maps fungerer

Da jeg arbejdede på Google Maps som UX Engineer, var en af ​​de ting, jeg virkelig ønskede at kunne gøre, at skabe prototyper, der kunne synkronisere animationer med zoom. Imidlertid har JavaScript Maps API begrænset kontrol af zoom, så for at få mere kontrol eksperimenterede jeg med at bruge HTML5 lærred til at sammensætte fliserne i en fuldt tilpasset implementering.

For at forstå, hvad der gør glat zoom udfordrende for moderne kortapplikationer, hjælper det med først at forstå, hvordan kortet er lavet, og hvordan Google Maps (og de fleste af de andre) kortklienter fungerer - du kan søge efter de udtrådne titler til at gå lige der.

  1. At gøre Jorden flad - processen, hvormed en 3D-klode omdannes til et 2D-kort.
  2. Google Maps - hvordan Google ændrede kortets verden i 2005, og hvordan klienten gengiver.
  3. Animering af det rasteriserede kort - den brugerdefinerede tilgang, jeg brugte for at muliggøre glat animation.

1. At gøre Jorden flad

Jeg håber, det ikke kommer som en overraskelse for nogen, men verden er rund - selvom det er underligt nok, er det faktisk ikke en sfære, men snarere lidt klemt og bredere i midten.

Processen med at tage dette mest sfæriske objekt og repræsentere det i 2D er noget, som kartografer har kæmpet med og kranglet over i årtusinder. Der er faktisk ingen "bedste" måde at gøre det på, hver kommer med tradeoffs.

Breddegrad og længdegrad

Hvis du forestiller dig verden som en kugle, der roterer rundt om sin akse, er nordpolen helt øverst, sydpolen er helt i bunden, og ækvator er den imaginære linje, der løber rundt i midten.

stregglas

Hvis du tænker på ækvator som en cirkel, der sidder vandret (som et bælte), kan du forestille dig yderligere vandrette cirkler over og under, hver af disse parallelt med ækvator. Følger en cirkel til højre, bevæger du dig mod øst, og følger enhver cirkel til venstre ret vest. Disse imaginære linjer kaldes breddegrad. Breddegradene er altid den samme afstand fra hinanden, uanset hvor langt øst eller vest du rejser.

Fordi jorden er vippet på sin akse, sidder solen ikke altid direkte over ækvator, men ser ud til at vandre lidt nord og syd under vores bane. Kræftens trop er den nordligste breddegrad, hvor solen kan optræde direkte over hovedet (på juni-solhøjden), og Stenbukken Tropen er ækvivalent på den sydlige halvkugle (og december solstice). Det er faktisk ikke vigtigt for Google Maps, men det er sjovt trivia.

Breddegrad angiver, hvor langt nord eller syd du er (fordi uanset hvor langt øst eller vest du går, har du slet ikke flyttet nord eller syd).

Kører i den anden retning, vinkelret på ækvator, er meridianerne eller længdegrader - forbinder hver nordpol mod syd i en lige linje. Efter enhver linje op, bevæger du dig mod nord og følger ned mod syd.

Længdegrad angiver, hvor langt øst eller vest du er (fordi uanset hvor langt nord eller syd du går, har du overhovedet ikke flyttet øst eller vest).

Den afgørende forskel mellem breddegrad og længdegrad er, at mens breddegrad altid er en lige stor afstand væk på en kugle, er længdegraderne længst fra hinanden i midten (ækvator) og kommer tættere på hinanden i nærheden af ​​polerne (hvor de rører).

Måling af breddegrad og længdegrad

Både breddegrad og længdegrad måles i grader. Fra midten af ​​sfæren måler du bare vinklen på hver cirkel (de parallelle breddegradskredse og de vinkelrette længdecirkler).

Breddegrad er den vinkel, der dannes fra ækvator, så den er 0º ved ækvator og op til 90º nord eller syd (ved polerne). Halvvejs mod Nordpolen ville være 45 ° N.

Længdegrad er vinklen målt fra Prime Meridian (en vilkårlig linje, der løber gennem Greenwich, England). Det er 0 ° i Greenwich og op til 180 ° øst eller vest (den modsatte side af verden).

Global positionering

En sidste bit trivia, der ikke rigtig betyder noget for Google Maps (men jeg finder ikke desto mindre interessant), er, hvordan folk lokaliserede sig inden GPS.

Latitude er relativt enkel, du skal bare måle vinklen mellem horisonten og en kendt stjerne (eller vores sol) og derefter lave en lille matematik. Folk har navigeret baseret på stjernerne i tusinder af år, Polaris (Nordstjernen) er især populær - på nordpolen er Polaris direkte overhead (90 ° til horisonten). Ved ækvator ser Polaris ud til at sidde i horisonten (ved 0 °). Mellem disse to ekstremer er vinklen til Polaris vinklen til breddegrad nord. Du kan også bruge andre stjerner, de kræver bare mere matematik.

Længdegrad er meget, meget, sværere - faktisk så hård, at ingen kunne gøre det godt før i slutningen af ​​1700-tallet (og heller ikke let før meget senere end det). Løsningen involverer tid - jorden roterer med samme hastighed, 360º hver 24 timer (eller 15º i timen), så hvis du ved tidspunktet, hvor du startede, og tidspunktet hvor du er, kan du beregne afstanden baseret på forskellen. Let at gøre i dag med nøjagtige ure, men meget svært at gøre tidligere.

Oprindeligt brugte de de forudsagte placeringer af himmellegemer på kendte tidspunkter for at beregne, hvad klokkeslættet var, og sammenlignede derefter det med den lokale tid (ved hjælp af solen ved middagstid for at nulstille ”det lokale ur”). Det mest udbredte sæt forudsigelser var den Nautical Almanac, der blev offentliggjort af Royal Observatory i Greenwich, England - som, hvis du nogensinde har spekuleret på, hvorfor det blev kaldt GMT (Greenwich Mean Time), eller hvorfor Prime Meridian løber gennem Greenwich, det for i den længste tid målte alle sig i forhold til observatoriet i Greenwich.

Det største gennembrud i måling af længdegrad var mere nøjagtige ure, så de kunne ophøre med stargazing og bare kontrollere GMT - længdegrad var antallet af timers forskel ganget med 15º. Faktisk er dette stort set, hvordan alle beregnet længdegrad indtil opfindelsen af ​​Global Positioning System (GPS), som kun er et sæt super nøjagtige atomur, der sidder i rummet (og nogle imponerende matematik).

Hvis du er halvt så fascineret af dette som jeg, vil jeg virkelig anbefale at læse denne fantastiske bog om den.

Valg af en projektion

Metoden til at oversætte punkterne på en 3D-klode til et 2D-plan kaldes en kortprojektion. Der er mange forskellige fremskrivninger, hver med deres egne styrker og begrænsninger, og ingen uden nogen form for forvrængning af den faktiske geometri - her er bare et par få:

Fremskrivninger på Wikipedia

Projektionen, som Google Maps valgte, er en modificeret version af Mercator-projektionen, kreativt titlet Web Mercator [den største forskel er, at det antager, at verden er en sfære, i stedet for en flad ellipsoid].

Der er nogle få grunde til at vælge Mercator, men den bedste grund er, at nord og syd er lige op og ned, og øst og vest er lige til venstre og højre - på nogle af de andre projektioner ville disse linjer krumme eller afvige, når du bevæger dig henover kortet. Dette ligner meget grunden til, at Mercator blev anvendt så vidt brugt til navigation - linjer med konstant kursus (dvs. hvis du vælger et kompaslager og holder dig ved det) er helt lige. En sidefordel er, at fordi det er en cylindrisk fremspring, kan du vikle vandret og flise kortet.

Nogle af de andre specielle egenskaber ved Mercator-projektionen er, at skalaen er den samme i alle retninger omkring ethvert lokaliseret punkt (dvs. hvis du zoomer ind i en by, er afstandene nord og syd den samme som øst og vest), og alle vinkler er afbildet nøjagtigt (dvs. øst er 90º fra nord).

Den største kritik af Mercator-projektionen er, at den markant forvrænger omfanget af lande, jo længere man kommer fra ækvator (f.eks. Grønland ser lige så stor ud som Afrika, på trods af at den er mindre end en tiendedel af størrelsen).

Tænk på, hvad der sker, når du skræler en appelsin, og forestil dig så at gøre det samme med jorden. Hvis du skulle skære langs serien med langsgående linjer, ville du ende med et sortiment af kilede skiver - på kloden vises hver lodrette linje lige, men når du pakker den ud til to dimensioner, bliver den buet (husk, hvordan disse længdegrader kom tættere sammen ved polerne?).

Frapakke kloden

For at få Mercator-kortet til at gå sammen igen, er vi nødt til at strække disse segmenter vandret. Ved ækvator er de allerede ved, så de behøver ikke at strække sig overhovedet, men ved polerne er der et rigtig stort hul, og de er nødt til at strække sig meget (teknisk uendeligt). For at bevare afstande og vinkler i begge retninger, når det strækker sig vandret, strækkes det med samme mængde lodret. Jo tættere det kommer til polerne, jo mere skal det strækkes.

Dette er lidt lettere at visualisere, hvis du prøver at tegne en cirkel i samme størrelse på forskellige punkter på kortet. Ved Mercator-projektionen kan du se, at cirklerne forekommer meget større mod polerne (selvom de i virkeligheden er nøjagtig samme størrelse). Dette skyldes, at vi havde brug for at strække kortet mere ved polerne for at få det til at oprette forbindelse.

Deformering af skala

Dette betyder virkelig kun, når der er meget zoomet ud, fordi skalaen er den samme i enhver lokaliseret region, så for en given by eller endda land forbliver alt proportional. Og det er virkelig kun et betydeligt problem for polerne, men da pingviner og isbjørne ikke bruger Google Maps, har der ikke været meget klage.

Hvis vi tænker tilbage på sfæren - hvor breddegrader var parallelle med hinanden og altid den samme afstand fra hinanden, mens længdegrader kom tættere sammen ved polerne - med Mercator, forbliver breddegrad perfekt parallelt, og længdegraden bliver perfekt vinkelret. Alt er lige.

Denne fantastiske video af Grafonaut demonstrerer hele transformationen:

2. Google Maps

I 2005 lancerede Google Maps ved hjælp af en innovation, der stadig understøtter hver eneste kortlægningstjeneste i dag - det flisebelagte kort. I 2013 var der en større opdatering for at bruge WebGL og tilføje rendering på klientsiden, men den flisebelagte tilgang er stadig.

Jeg skal bemærke, at Google Maps ikke opfandt konceptet med det flisebelagte kort, men det var måske den første mainstream-applikation, der brugte det, og at kombinere det med AJAX og internettet har bestemt hjulpet med at popularisere fremgangsmåden.

Flisebelagte kort

I stedet for at prøve at gengive et enkelt billede, opdeler Google kortet i mindre fliser og placerer dem derefter ved siden af ​​hinanden for at udgøre et enkelt større billede - ligesom en mosaik.

Flisebelagt kort

Den primære årsag til dette er billedstørrelse. På det højeste zoomniveau på Google Maps ville billedet være over 500 millioner pixels kvadrat (dobbelt så stort som på HDPI-skærme), hvilket er mere end 25.000 terabyte (tror jeg?) Selv med generøs optimistisk billedkomprimering. Hvis du antager, at din browser kunne gengive dette billede, vil det tage dig mere end 6 år at downloade ved hjælp af Google Fiber.

Den anden grund er serverbelastningen. I stedet for at bruge fliser, kunne serveren generere et kort i perfekt størrelse til hver bruger på det nøjagtige zoomniveau, breddegrad og længdegrad og den rigtige størrelse til at udfylde deres vindue. Men det vil sandsynligvis betyde, at hver bruger har brug for et helt tilpasset kort, og med over 1 milliard månedlige brugere er det en masse tilpassede kort! Det ville også betyde, at hver gang du panorerede kortet, selv et par pixels, skulle du downloade et helt nyt kort.

Det fine ved fliser er, at alle kan dele dem, serveren kan cache dem (og endda præ-generere dem), og klienten kan nemt flytte dem. Nogle brugere downloader muligvis et par ekstra fliser, hvis deres vindue er større, men fliserne behøver stadig kun gengives en gang.

Bemærk: til den vektorbaserede WebGL-implementering i 2013, i stedet for at sende billedfliser, sender serveren vektoroplysninger (hver sti og polygon), og klienten gengiver billedet. Imidlertid grupperes denne vektorinformation stadig i “fliser” - af nøjagtige samme grunde (tillader servercache, og giver en ren måde for klienten at opdele dataforespørgsler). Mens de følgende afsnit diskuterer rasterimplementeringen (som stadig bruges af Maps JavaScript API), gælder den generelle tilgang også for WebGL-versionen.

Zoomniveauer

Google Maps har et varierende antal zoomniveauer baseret på placeringen - men det er normalt ca. 21. På det mest zoomede (niveau 0) er hele kortet repræsenteret af en enkelt 256 med 256 pixel firkantet flise. Ved hvert trinvis zoomniveau fordobles kortet i størrelse i hver retning - hver flise erstattes af 4 mere detaljerede (2x2), når du zoomer. Hver flise er stadig kun 256 x 256 pixels, og når du kombinerer dem sammen får du det samme kort (kun mere detaljeret).

På zoomniveau 0 er verdenskortet en enkelt flise, ved zoom 1 er kortet 2 fliser i hver retning, ved zoom 2 er det 4 fliser bredt, ved zoom 3 er det 8 fliser bredt og så videre (fordobling hver gang) . Så mens den samlede bredde og højde fordobles hvert niveau, går området hurtigere op (1 flise, 4 fliser, 16 fliser, 64 fliser osv…). På det tidspunkt, det rammer zoomniveau 21, er kortet 2 millioner fliser bredt og indeholder mere end 4 billioner fliser i alt.

Kortfliser på hvert zoomniveau

Hvert zoomniveau får sine egne stilregler til at bestemme, hvilke oplysninger der skal vises. Der er ikke meget værdi at tilføje vejoplysninger til verdenskortet og heller ikke opbygge oplysninger til landekortet osv.… Der er et meget hårdtarbejdende team, der konstant afbalancerer etiketter og funktioner, der præsenteres og styles på hvert niveau.

Som en generel tommelfingerregel er de første par niveauer stort set bare verdenskortet. Ved zoom 5 er kontinenter og landmasser de primære funktioner. Efter niveau 10 kommer byoplysningerne frem. På niveau 15 er gaderne tydeligt synlige. Og med zoom 20 er bygningerne alle gengivet.

Vi kan nemt estimere pixelskalaen for disse zoomer - vi kunne gøre det præcist med mere kompleks matematik - men ved ækvator (hvor Mercator-projektionen ikke strækker kortet) er det en simpel beregning:

Ved zoom 1 repræsenterer hver pixel 78 km (48 miles), zoom 5 er 5 km (3 miles), zoom 10 er 150 m (164 yards), 15 er 5 m (5,5 yards), og ved zoom 20 er hver pixel ækvivalent med 15 cm ( 6 tommer) - det er imponerende detaljeret!

Positionering

Google Maps har tre begreber koordinater ud over breddegrad og længdegrad: verdenskoordinater, pixelkoordinater og flisekoordinater.

Verdenskoordinater er uafhængige af zoomniveauet og bruges til at oversætte mellem breddegrader og længdegrader og den aktuelle position på kortet (eller omvendt). De beregnes i forhold til en enkelt flise på zoomniveau 0. Breddegrad og længdegrad er kortlagt til den brøkdelte x og y-pixel på den ene flise (et tal mellem 0 og 256 - dets bredde og højde).

Konverteringen er meget let, selvom jeg ikke kan påstå, at jeg forstår matematikken. Længdegradskortningen er let at forstå, da den direkte oversættes, men breddegraden er mere kompliceret på grund af skævheden, når den nærmer sig polerne.

Pixelkoordinater refererer til den nøjagtige pixelposition for en breddegrad og længdegrad på et specificeret zoomniveau. De kan beregnes ved at tage verdenskoordinaterne og multiplicere med det samlede skaleringsmængde for zoomniveauet - hvilket er let at beregne, fordi skalaen fordobler hver zoom.

Flisekoordinater er, hvordan klienten beder serveren om billeder.

Flisekoordinater

De er placeret i rækker og kolonner, med række 0 kolonne 0 øverst til venstre, rækker øges til højre og kolonner, når du går ned. I lighed med pixelkoordinater er fliser afhængige af zoomniveauet, det er faktisk en simpel kortlægning fra pixel til flise ved at dele pixelkoordinaterne med flisestørrelsen og tage det integrerede nummer.

Klienten kan nemt finde ud af, hvilke fliser den har brug for ved at beregne flisekoordinaterne for hvert hjørne af skærmen. Normalt tilføjes det en lille smule polstring, som et middel til at indlæse, bare i tilfælde af at brugeren panorer kortet med et par pixels.

Kunder kan nemt generere flise-URL'er ved hjælp af disse koordinater - for eksempel zoomniveau 1, række 0, kolonne 0, er denne flise, der indeholder Nordamerika. Det er trivielt at konstruere i kode, skønt Maps API håndterer det for dig (sammen med styling og andre fordele).

Panorering

Når man interagerer med kortet, er fliserne virkelig bevise, at de er værd. Før Google Maps fungerede tingene lidt mere som en Atlas - hvis du kom til kanten af ​​kortet, var du nødt til at dreje siden for at se noget mere. Det fantastiske ved fliser er, at det er muligt for brugere at frit udforske kortet uden afbrydelse, da de panorerede og zoomede rundt.

Hver flise er placeret absolut inden i en container, og når du panorerer kortet i stedet for at flytte hver flise, er det kun beholderen, der behøver at flytte (og fliserne skifter med den). Dette gør det muligt for klienten at minimere antallet af DOM-ændringer.

Beholderen bevæger sig, ikke fliserne

Det er nemt at beregne den placering, som hver flise skal være i ved blot at multiplicere flisekoordinaten med flisestørrelsen.

Det sidste placeringstrik minimerer det samlede antal fliser, som browseren skal gengive.

Opretholdelse af en konstant størrelse DOM

Når brugeren panorerer kortet, kontrollerer klienten, hvilke fliser der skal være synlige og enten indlæser de nye, eller fjerner dem, der ikke længere er synlige.

Dette gøres så hurtigt, at brugeren sjældent lægger mærke til (og i stedet for at klippe til de hårde grænser for skærmen henter den ofte ekstra få fliser på hver side som en buffer). [Denne tilgang er faktisk ikke så forskellig fra, hvordan Google Fotos nu fungerer]

Zoom

Panorering er problemfri, men zoom er et af udfordringsområderne med et flisebelagt kort.

På den tekniske side er udfordringen mindre, hvordan man placerer fliserne, men mere hvordan man skifter mellem niveauer. Hvert zoomniveau er en fordobling i skalaen, og der er ingen mellemfliser, der kan hjælpe.

Snapping mellem niveauer

Zoomning var oprindeligt meget simpelt, det erstattede netop kortet med det næste sæt fliser, men det var lidt skurrende, fordi det pludselig ville "klikke" mellem niveauerne.

En måde, dette kompenseres for, er i stedet for at zoome ind på midten af ​​kortet, det holder uanset hvilken placering der er under markøren stille (fastgjort under markøren). Dette gør det muligt for brugere bogstaveligt talt at "pege" på den funktion, de er interesseret i og fokusere på (de kontrollerer referencepunktet).

Hurtig skala-animation (Maps JavaScript API)

En nyere tilpasning fik den til at føles mere lydhør. Når du zoomer, holder det midlertidigt begge zoomniveauer værd for fliser (de gamle og nye) og udfører en meget hurtig skala-animation imellem dem - de nye fliser begynder at blive nedskaleret med halvdelen, og de gamle fliser animeres op til dobbelt størrelse.

Det "klikker" stadig mellem lagene, når animationen er færdig, men det hele sker så hurtigt, at øjenformen forestiller sig mellemtilstanden.

Denne skala & snap-tilgang er det, der stadig bruges i dag af Google Maps JavaScript API, Bing Maps, Here Maps, Yahoo Maps, MapQuest og OpenStreetMap (LeafletJS).

Skala overgang under zoom

Muligheden for den største begrænsning med skala & snap er, at brugeren ikke har kontrol over animationen, når de først udløser zoom, kører den, indtil den er afsluttet, er der ingen mulighed for at kontrollere hastigheden eller pause i en mellemtilstand.

Vector kort

I 2013 frigav Google Maps en større opdatering til maps.google.com, der stoppede med at bruge PNG-fliser til billeder og begyndte at downloade vektorfliser. Disse vektorfliser henvises stadig til ved deres flisekoordinater, opfører sig stadig meget på lignende måde som rasterfliserne, men i stedet for at være et billede, indeholder de alle etiketter, stier og polygoner - og tegnes på klienten.

Der er forskellige årsager til, at dette er vigtigt - vektordataene komprimeres bedre end billeder (så sparer båndbredde), det muliggør dynamiske opdateringer og styling (for eksempel hvis en bruger klikker på en transitrute), og det muliggør en betydelig forbedret zoom .

Glat zoom

Med de rasteriserede PNG'er, som du skalerer, kan kortet ikke fortælle, hvad der er en vej (og skal forblive tegnet i samme bredde), eller hvad er en park (og skal skalere større), hvilket betyder, at alt bare bliver strakt og pixeleret. Med vektorinformationen kan klienten holde etiketter korrekt placeret, vedligeholde vejbredderne og alligevel skalere alle polygoner - resultatet er en utrolig glat zoomoplevelse. Endnu bedre er det helt lydhør, så brugeren kan kontrollere hastigheden og endda stoppe ved fraktioneret zoomniveau.

Hvis du imidlertid ser nøje (eller går og prøv dig selv), mens det er meget glat at skalere fliserne, er der ingen nye oplysninger, før du stopper. Så snart brugeren sætter zoom på pause, indlæser kortet hurtigt vektorfliserne på det nye zoomniveau og udskifter dem. Det er virkelig glat & snap.

MapBox er en af ​​de andre andre klienter, der bruger vektorfliser på nettet, og bruger også denne glatte & snap-tilgang, selvom de mere aggressivt indlæser nye fliser under zoomovergange.

3. Animering af det rasteriserede kort

For at være i stand til jævnligt at animere kortet er det mest kritiske element muligheden for at indstille fraktionerede zoomniveauer (f.eks. Halvvejs mellem to af de integrerede zoomniveauer, som fliserne gengiver på). Med vektorfliser kan dette tilpasses på klienten, men med rasterfliserne er vi nødt til at blive mere kreative.

For at understøtte det skrev jeg en fuldt tilpasset version af Maps JavaScript API, der genbruger billedfliserne fra Maps Server, men derefter placerer dem og håndterer interaktionen i sig selv. Dette muliggjorde fuld kontrol over skalering og placering af hver flise samt kontrol over zoom og animation. Alle fortalte, at det var 4.442 linjer med kommenteret kode - det er bemærkelsesværdigt, hvor lidt kode klienten har brug for, men hvis du tænker over det, udføres det meste af det virkelig hårde arbejde på serveren (ved at finde ud af hvilke veje, søer, bygninger osv. ... er synlige i hver flise, beslutter typografier og farver og gengiver det derefter som billeder).

Resten af ​​denne artikel henviser til min prototypekode og ikke til den normale version af Google Maps.

Oprettelse af delvis zoomniveauer

I lighed med fremgangsmåden på Google Fotos til at krydse opaciteten mellem lavopløsnings- og højopløsningsbilleder for at blande detaljerne under indlæsningen, var min teori, at vi kunne blande fliserne fra forskellige zoomniveauer for at skabe en mellemtilstand.

Vi kunne tage lavere zoomniveauer og skalere dem op, som når du zoomer ind, eller gøre det modsatte og skalere de højere zoomniveauer, når du zoomer ud. Ved at skalere og overlægge fliser fra flere zoomniveauer kunne vi jævnligt skifte mellem integrerede zoomer og gøre det på en matematisk forudsigelig måde (perfekt til synkronisering af animationer).

Dette er muligt, fordi Google bruger Mercator-projektionen - skalaen er ensartet for lokaliserede regioner, så lineær skalering af fliserne bevarer figurerne.

Beregning af opaciteten for krydsfaden er enkel og lineær. Ved overgang mellem to zoomniveauer skal den næste flise være fuldt gennemsigtig (opacitet 0) ved det første zoomtrin og fuldt uigennemsigtig (opacitet 1) ved det næste. Så opacitet er kun 1 minus afstanden fra flisezoomen fra kortzoomen (selvom den i praksis klemmes til værdier mellem 0 og 1).

Skalaen er heller ikke så kompliceret. I betragtning af at skalaen fordobles på hvert zoomniveau er det muligt at beregne størrelsen på skalaen for et mellemliggende niveau ved hjælp af kræfter på 2.

Hvis mapZoom er et fuldt niveau, der er højere end flisen (mapZoom - tileZoom = 1), ville matematikken resultere i 2 If eller 2. Hvis mapZoom er det samme niveau som flisen (mapZoom - tileZoom = 0), ville matematikken være 2⁰ eller 1. Det store ved beregning af kræfter er, at det fungerer til brøk og negativ. Hvis mapZoom er et fuldt niveau lavere end flisen (mapZoom - tileZoom = -1) får du 2⁻¹ eller 0,5. Halvvejs mellem zoomniveauer (mapZoom - tileZoom = 0.5) får du 2 ^ (0.5) eller 1.414, og du kan gøre dette for at skalere jævnt mellem hver tilstand.

Følgende illustration viser, hvordan dette gælder, mens du zoomer ind. Den monokrome flise er startzoomniveauet, og de farvede fliser er det næste zoomniveau (tegnet i et tavle, så du kan se forskellen). Du kan se, at jo tættere vi kommer på det næste zoomniveau, jo mere dominerer de farvede fliser, og detaljerne (f.eks. Etiketter) begynder at falme ind.

Zoom ind

Her er den modsatte retning ved at zoome ud. Monokrom er det zoomende niveau igen, og farvede det forrige zoomniveau - i dette tilfælde mister vi detaljerne, mens vi zoomer ud.

Zoom ud12fps

Det fungerer meget godt, hvilket giver en relativt glat zoom (meget bedre end skala & snap), og selvom den ikke er så glat som vektor gengivelsen (glat & snap) eliminerer det faktisk snap-aspektet helt.

Jeg har overført debugoverlayet i GIF, så du kan se, hvor de nye fliser indlæses. Det er glattere i praksis (60 billeder i sekundet), men jeg begrænsede GIF til 12 fps. Her er videoen. Alternativt er det her uden debuglinjer.

På begge ekstreme er kortet næsten umærkeligt anderledes end den integrerede zoom, skønt det i midten kan komme i en akavet tilstand med etiketter, der konkurrerer med hinanden. Dette ville ikke være en stor effekt, hvis man opholder sig på et fraktioneret zoomniveau, men det er ikke et problem under overgange - og for at undgå fraktioneret zoom er det nemt for klienten at "sætte sig" tilbage til en integreret zoom, når brugeren er færdig med at zoome, venter på, til de giver slip, så de forbliver i fuld kontrol i mellemtiden.

Zoom mellem integreret niveau 3 og 4

Skal vi kalde denne effekt skala & blanding? Det zoomer ikke kun glat, men det reagerer altid på brugerinput.

Animering af zoom med 24 fps

HTML5 lærred

For at opnå den bedste ydelse forlod jeg den normale web-tilgang til at bruge standard HTML-elementer. Som beskrevet i det foregående afsnit bruger Maps API en kombination af DIV og IMG-elementer til at gengive kortet. Hvert billede er placeret i en overordnet beholder, og den beholder flyttes som kortpander. For normale webapplikationer, der lader browseren styre ting, har en masse fordele, browseren håndterer alle beslutninger omkring, hvornår man skal tegne skærmen på ny, hvordan man skal placere og placere elementer, samt strømline interaktion (som rulning og klikbegivenheder). At udnytte browseren til dette er næsten altid den bedste metode.

Nogle gange - især til meget tilpasset tegning - er det dog fordelagtigt at gøre dette manuelt. For at understøtte dette oprettede browsere lærredselementet. I modsætning til normal HTML, hvor du opretter elementer og føjer dem til siden, med et lærred udfører du tegnekommandoer på det. Du kan tegne linjer, buer, cirkler, rektangler og endda komplekse kurver. Lærredet gør ikke noget ekstra for dig og behandler resultatet ligesom et billede. Hvis du tegner et billede ind på lærredet, skal du fortælle det nøjagtigt, hvor og hvilken størrelse, og så hvis du beslutter at flytte billedet et par pixels, skal du rydde hele lærredet og male alt på det (inklusive de andre linjer og billeder). Hvis du ruller på et normalt websted, beregner browseren positioner og tegner om igen. Hvis du vil lade brugere rulle et lærred, skal du manuelt genberegne hver position og derefter tegne om hvert stykke selv.

Hvis det lyder som en masse arbejde, er det virkelig, og det er en af ​​de mange grunde til, at lærred ikke bruges oftere; men der er visse situationer, hvor et lærred er den bedste måde at opnå noget, og en fuldt tilpasset kortudgiver er en af ​​dem (da skalering og placering af hver flise var brugerdefineret alligevel, ved hjælp af et lærred tilføjede en vis kompleksitet men reducerede overhead) .

En måde at visualisere forskellen i, hvordan siden konstrueres, er ved at se på den resulterende HTML. Den normale tilgang skaber snesevis af elementer og tilføjer dem til siden, men for lærredstilnærmelsen er der meget lidt der, alt var en tegnekommando inden på lærredet.

Forskel i sidestruktur mellem elementbaseret og lærredstilgang

Fallback fliser

En kantkasse, der skal håndteres, når man skalerer og blander fliserne, er hvad der sker, hvis der ikke er nogen fliser, der skal blandes. Hvis den nedre zoom ikke er indlæst endnu, så i stedet for at animere opacitet på det næste niveau, er det bedre at starte den helt uigennemsigtig - ellers vil der være ujævn huller på kortet.

Beregning af dækningen af ​​tilbagelagte fliser er et område, som den brugerdefinerede lærredsammensætning virkelig lyser - hver tegneoperation kan indstille tilpasset opacitet, og fordi lærred virkelig er effektiv til små tegneoperationer (f.eks. Kortplades størrelse), kan vi effektivt indstille tilpassede opacitet til hver enkelt flise. Hvis vi stolede på browseren til at gøre dette med brugerdefinerede elementer, kunne alle de individuelle animationer forårsage afmatning.

Klienten trækker altid tilbageslag (de underliggende) fliser ved fuld opacitet og ændrer kun opaciteten af ​​de primære (målzoomniveau) fliser, som den overlejrer ovenpå. Før malingscyklussen kontrollerer det, om den primære flise har fuldstændig tilbagefaldsdækning - dvs. om alt under denne flise er tegnet.

For eksempel, hvis vi zoomede ind her, fra zoom 0 til 1 - de farvede fliser har fuldstændig dækning, og den enkelt grå flise under dem overlapper fuldt ud. Dette betyder, at de med sikkerhed kan få deres opacitet animeret uden at efterlade hullerne.

Det modsatte er dog ikke sandt. Hvis vi zoomede ud, fra 1 til 0 (så de farvede fliser ville være underlaget), kan vi ikke animere opaciteten på den grå flise, fordi det ville efterlade nogle huller.

En mulig løsning på dette ville være at male de primære fliser uden dækning en gang som et fuldstændigt uigennemsigtigt bundlag, derefter male maling af fliserne og derefter sikkert ændre den primære flisens opacitet (i værste fald ville det blandes med sig selv) - men det ville øges antallet af trækningsoperationer for lidt mærkbar forbedring.

En anden ting, vi kan gøre med uigennemsigtigheden, er at hurtigt animere i nylastede fliser, så i stedet for at klikke på plads, er der en hurtig fade.

Zoomretning og hastighed

Klienten holder også styr på, i hvilken retning brugerne zoomer (ind eller ud), samt hvor hurtigt de zoomer, og bruger dette til at bestemme, om den skal indlæse nye fliser.

For eksempel, hvis du zoomer ind fra 14 til 15, gider klienten ikke at indlæse flere fliser fra zoomniveau 14, men prioriterer i stedet hentning af de nye, den har brug for fra 15. Hvis det modsatte fandt sted, zoomes ud fra 15 til 14, ville klienten kun prøve at indlæse de nye fliser fra 14.

Den bruger zoomhastigheden til at bestemme, om der overhovedet er noget punkt at indlæse fliser, eller om det er sandsynligt, at laget zoomes forbi, før billederne har en chance for at indlæse. For eksempel, når du zoomer hurtigt, kan brugeren køre hele vejen fra zoom 4 til 15 på bare et sekund - og der er lidt point ved at indlæse 5, 6, 7, 8 ... fordi det bare ville være spild af fliser. Til zoom kan vi heldigvis skalere kun et par fliser (husk ved zoom 0, flisen repræsenterer hele verden) for at holde farverne / figurerne vagt repræsentative.

Undgå nedskalering

I betragtning af, at kortet bliver mere detaljeret ved højere zoomniveauer, antog jeg naivt, at det ville være at foretrække at bruge de højere niveauer og nedskalere dem, når jeg zoomer ud.

Nedskalerer for mange fliser

Men i praksis fungerede dette ikke særlig godt - mens skalaen for alle polygoner (f.eks. Vand og parker) er godt bevaret, bliver alle etiketter og ikoner også skaleret, hvilket gør et kort der ser virkelig rodet ud, især i midten. Forestillingen fik også et stort hit, for i stedet for at tegne et par dusin fliser var der pludselig hundreder tilgængelige for tegning.

For at undgå dette deaktiverede jeg nedskalering af fliser med mere end 1 zoomforskel. Dvs. når der zoomes i fraktionernes niveauer af 14'erne (f.eks. 14.2), kan det bruge fliser fra zoom 15, men aldrig, nogensinde, fliser fra 16 eller højere.

Animationer

Min erklærede hensigt i starten af ​​projektet var at være i stand til programmatisk at synkronisere animationer med zoom, selvom det også har den sidefordel ved bare at føle sig bedre - det er glattere og mere lydhør over for brugerinput. Når det er integreret zoomniveau, opfører det sig det samme som den normale implementering. Jeg synes dog, det er ret effektivt til animering - du kan selv dømme.

Her er et eksempel, der visualiserer en mulig flyvevej fra San Francisco til Australien. [alternativt den fulde video, eller en med debugoverlayet]

Flyver (og glat animerer) fra San Francisco til Australien

Der er selvfølgelig ingen grund til, at det skal være begrænset til zoomanimationer. En yderligere fordel ved at male alt på et lærred er, at det kan lade os gøre nogle virkelig kreative ting, hvis vi vil.

destination-out globalCompositeOperation

Vist er et eksempel ved at bruge destination-out-sammensætningstilstand på lærred for at “skære” figurer ud af kortet - her overlejres et monokromt kort oven på et farvet kort og animerer derefter afsløringen. [fuld video]

Fremtidig brug

Så sjovt som det var at skrive, og så nyttigt som dette var til min prototype (det drev dusinvis af prototyper i løbet af min tid på teamet) har Google og MapBox begge vektor gengivelse, der overgår denne skala & blandingsmetode. Jeg vil med glæde opfordre alle andre, der ikke opgraderer til vektorfliser, til at overveje at implementere noget lignende. Det er en temmelig enkel teknik, der ikke tilføjer klienten meget kompleksitet (bestemt mindre arbejde end vektor gengivelse), men som muliggør en meget mere glat og lydhør oplevelse.