diff --git a/CHANGELOG.md b/CHANGELOG.md index 3748afe85d3..a880dbce258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## master + +#### Breaking Changes + + * The `GeoJSONSource`, `VideoSource`, `ImageSource` constructors are now private. Please use `map.addSource({...})` to create sources and `map.getSource(...).setData(...)` to update GeoJSON sources. + ## 0.21.0 (July 13 2016) #### Breaking Changes diff --git a/debug/circles.html b/debug/circles.html index 1591e44b79a..b9e9a1e6a50 100644 --- a/debug/circles.html +++ b/debug/circles.html @@ -55,7 +55,7 @@ + + + + + + diff --git a/debug/image.html b/debug/image.html new file mode 100644 index 00000000000..1e9d82dda20 --- /dev/null +++ b/debug/image.html @@ -0,0 +1,60 @@ + + + + Mapbox GL JS debug page + + + + + + + +
+ + + + + + + + diff --git a/debug/index.html b/debug/index.html index 3a603e6a55f..86c4bad14c3 100644 --- a/debug/index.html +++ b/debug/index.html @@ -67,7 +67,7 @@ map.on('load', function() { map.addSource('geojson', { "type": "geojson", - "data": "route.json" + "data": "mapbox-gl-test-suite/data/linestring.geojson" }); map.addLayer({ diff --git a/debug/featuresIn.html b/debug/queryFeatures.html similarity index 69% rename from debug/featuresIn.html rename to debug/queryFeatures.html index 9f2d6a525eb..4e29ce588de 100644 --- a/debug/featuresIn.html +++ b/debug/queryFeatures.html @@ -21,22 +21,17 @@ var map = new mapboxgl.Map({ container: 'map', zoom: 12.5, - center: [38.888, -77.01866], + center: [-77.01866, 38.888], hash: true, style: 'mapbox://styles/mapbox/streets-v9' }); map.addControl(new mapboxgl.Navigation()); -var querySource = new mapboxgl.GeoJSONSource({ - data: { type: 'FeatureCollection', features: [] } -}); -var boxSource = new mapboxgl.GeoJSONSource({ - data: { type: 'FeatureCollection', features: [] } -}); +var emptyFc = { type: 'FeatureCollection', features: [] } map.on('style.load', function() { - map.addSource('queried', querySource); + map.addSource('queried', { type: 'geojson', data: emptyFc }); map.addLayer({ "id": "query", "type": "line", @@ -47,7 +42,7 @@ } }, 'country-label-sm'); - map.addSource('boxsource', boxSource); + map.addSource('boxsource', { type: 'geojson', data: emptyFc }); map.addLayer({ "id": "box", "type": "fill", @@ -81,28 +76,22 @@ [boxcoords[1][0], boxcoords[0][1]], boxcoords[0] ]; + var results = map.queryRenderedFeatures(box, {}) + map.getSource('queried').setData({ + type: 'FeatureCollection', + features: results + }); - console.log('box', boxcoords); - map.queryRenderedFeatures(box, { - includeGeometry: true - }, function (err, results) { - console.log('queryRenderedFeatures', box, err, results); - querySource.setData({ - type: 'FeatureCollection', - features: results - }); - - boxSource.setData({ - type: 'FeatureCollection', - features: [{ - type: 'Feature', - properties: {}, - geometry: { - type: 'LineString', - coordinates: boxcoords - } - }] - }); + map.getSource('boxsource').setData({ + type: 'FeatureCollection', + features: [{ + type: 'Feature', + properties: {}, + geometry: { + type: 'LineString', + coordinates: boxcoords + } + }] }); } }); diff --git a/debug/random.geojson b/debug/random.geojson deleted file mode 100644 index 8f4e71844ed..00000000000 --- a/debug/random.geojson +++ /dev/null @@ -1 +0,0 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[149.49637136422098,-9.654809371568263]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-32.20742748118937,18.248684629797935]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-136.7495269048959,-27.38302533980459]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[57.49033374711871,-2.0502609573304653]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-35.09258760139346,-48.48457373678684]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.515633599832654,40.50837479531765]},"properties":{"mapbox": 18}},{"type":"Feature","geometry":{"type":"Point","coordinates":[87.57965345866978,88.35084596648812]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[48.276180010288954,-75.54946155287325]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-155.12388995848596,6.622756691649556]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[94.88623925484717,49.332872168160975]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[60.52322088740766,3.2778834644705057]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-63.66609351709485,53.50058400537819]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[7.593629099428654,-79.76764933671802]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[95.09749154560268,-47.59878045413643]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-29.926261017099023,6.000985521823168]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[24.312616987153888,-10.033922642469406]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[120.91760211624205,-72.98863234464079]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[176.1061437986791,-28.75677346251905]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-103.94753599539399,-66.18871176615357]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.15921684354544,43.33588196430355]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[120.14222511090338,65.34176852088422]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[162.69001602195203,-20.188797153532505]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-132.7384481113404,59.35609757434577]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[151.21787237003446,-55.01443739980459]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[48.94104430451989,17.261870596557856]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[110.3044160362333,-9.162420881912112]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[24.650879502296448,37.41224037017673]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[6.55064650811255,-80.02935272175819]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-80.96966198645532,-22.852723225951195]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-5.970262913033366,4.047233420424163]},"properties":{"mapbox": 78}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-56.763304229825735,-39.60214911494404]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-95.64289779402316,35.48857259564102]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[169.02061921544373,58.58780784532428]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-49.70909879542887,-22.62584966607392]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[54.00681495666504,39.8978583002463]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[135.5449115484953,32.358882520347834]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[24.55371782183647,45.599905801936984]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[69.13071971386671,-12.706649419851601]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[26.994442865252495,46.82549917604774]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-162.23010728135705,77.0919276215136]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[14.995029559358954,30.44066105503589]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.0795504618436,43.8307428220287]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-104.52400088310242,84.28086372558028]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-83.39116405695677,80.11053497437388]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-169.15256646461785,29.123284625820816]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[135.97556702792645,-15.959288226440549]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-0.24300578981637955,-77.82989162486047]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[38.77691642381251,42.55800019484013]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[95.83638723939657,-78.0720021435991]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[132.05492452718318,-60.289104734547436]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-79.45233182050288,24.808030799031258]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[149.3677013553679,-58.10220593120903]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-94.85853572376072,7.255792384967208]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[158.3035398647189,53.35667263716459]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-110.09574243798852,-43.99398373905569]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-52.67722936347127,27.093252972699702]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[150.15035980381072,-80.53846763446927]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[9.024425586685538,-28.18460740149021]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-51.82950630784035,20.498926313593984]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-13.797060800716281,-42.18382612802088]},"properties":{"mapbox": 18}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-117.65530060976744,-9.724032743833959]},"properties":{"mapbox": 22}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-96.10944810323417,-82.18850092962384]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.59358338825405,-74.11594199482352]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-18.176996894180775,21.17788129951805]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-51.399409398436546,39.13681254722178]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[21.84296532534063,-35.34000772051513]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-169.2613579519093,73.16755077335984]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-145.11284035630524,-78.65141767077148]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[125.41096463799477,-78.09826633427292]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-43.089810283854604,-63.84157964028418]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-55.58937284164131,61.76249725744128]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[2.9992335475981236,34.93651384022087]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-179.1684382595122,-36.7301441822201]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[3.758228961378336,-36.35523778852075]},"properties":{"mapbox": 22}},{"type":"Feature","geometry":{"type":"Point","coordinates":[126.35871229693294,-73.91656078398228]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[90.33874975517392,50.97015139181167]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[123.69971914216876,-40.703937211073935]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[23.663350408896804,-5.27460019569844]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-166.637580441311,-48.44638771377504]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.89038298279047,-37.10679828654975]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-34.29882380180061,19.043962117284536]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[71.50328806601465,61.23050490394235]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[5.372699433937669,-13.683151029981673]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[15.534586999565363,63.58376717660576]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-27.496591536328197,80.22686891723424]},"properties":{"mapbox": 50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[102.24426973611116,2.16921198181808]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.82710740156472,-45.28801170643419]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-118.21109589189291,-39.60671909619123]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[46.47027407772839,-63.175592171028256]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-119.10293164663017,54.71257191617042]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[78.5866369586438,14.075388433411717]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-38.26819198206067,-72.82141484320164]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[134.69311656430364,-38.25590373482555]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-177.73576171137393,82.00566588900983]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-55.613233521580696,31.487578321248293]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-168.1243485957384,40.77788412105292]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[154.12042871117592,-20.77773309778422]},"properties":{"mapbox": 37}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-15.718078510835767,20.40679955855012]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-176.5286646410823,-70.40267100557685]},"properties":{"mapbox": 28}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-32.08226814866066,75.64151232596487]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-27.555824434384704,71.51631538756192]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[48.07085330598056,42.78952797409147]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-9.559019934386015,-40.79291358590126]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-85.9208913333714,-51.566796214319766]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[55.81204514019191,-16.223004986532032]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[61.25111969187856,0.04141175653785467]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[132.91573802009225,-78.94584295805544]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-97.36254681833088,-24.4186201505363]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[14.804459111765027,-84.26275152247399]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-59.36579857952893,86.52482149656862]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-143.1278306711465,43.04532102774829]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.85176498815417,-4.281637282110751]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[138.59302651137114,-25.457210708409548]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-61.750815231353045,-78.79723655059934]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-131.30710705183446,2.1586280269548297]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-30.80566104501486,89.74685768131167]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-113.42367156408727,-27.952353372238576]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[65.66230519674718,-22.028965833596885]},"properties":{"mapbox": 37}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-92.10858978331089,-38.35110099054873]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-154.14954258129,80.32227404415607]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-19.887921614572406,-64.32063193526119]},"properties":{"mapbox": 28}},{"type":"Feature","geometry":{"type":"Point","coordinates":[125.35523537546396,-35.98540982231498]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[31.097621507942677,-89.00853195693344]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[14.43086595274508,84.92379216011614]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.901069736108184,-22.19181087333709]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[97.93589439243078,-4.197207251563668]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-38.304785611107945,-69.23459123354405]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-173.49190623499453,-61.27010130789131]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[0.9125038515776396,59.265824225731194]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-143.5661626327783,-19.61784667801112]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[140.19756828434765,45.24801858700812]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-146.82779006659985,44.627577625215054]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.03192960098386,-43.90957793220878]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.60585057735443,-55.70240263827145]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[80.67271177656949,-25.16001457348466]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-37.23169421777129,41.82630505878478]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[14.471303941681981,-67.27960975840688]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[29.351948769763112,-37.61261290870607]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-118.98626661859453,85.98206254653633]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-133.93749067559838,-52.089063758030534]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-49.4992710929364,53.66898624692112]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.8944896273315,-31.78370848763734]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[143.98385466076434,61.59199266694486]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-145.0802127085626,-10.938919996842742]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[169.7884490713477,52.00517109129578]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-172.17302148230374,-42.275232123211026]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[125.76004978269339,33.90714329201728]},"properties":{"mapbox": 50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.21696455217898,89.54513410571963]},"properties":{"mapbox": 56}},{"type":"Feature","geometry":{"type":"Point","coordinates":[159.5182471163571,7.914179898798466]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-160.38636619225144,9.510616078041494]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[109.20965918339789,81.20597960427403]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-25.193190816789865,54.03361869044602]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[151.75552938133478,1.9545546267181635]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-179.74409027025104,84.30114571005106]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-21.349562080577016,86.70269302558154]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-88.04809698835015,1.114083006978035]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-1.8410969339311123,82.13660018518567]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[131.50190021842718,5.552907628007233]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-92.0619879104197,10.845119752921164]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-48.7168724834919,48.143608435057104]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[117.41884041577578,13.289493229240179]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[64.85487101599574,-8.208351884968579]},"properties":{"mapbox": 80}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.34714634716511,1.4469616999849677]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-93.29159198328853,-72.4770661862567]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[66.42859940417111,-78.27111136168242]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[96.73809905536473,19.116298486478627]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-61.793883545324206,72.12481695692986]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-98.2477710954845,37.38704076502472]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[148.8963887002319,-50.787345939315856]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[87.8617275506258,-77.5012998143211]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.15870479680598,-3.630034038797021]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-131.9270085543394,-68.15944958478212]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[167.64817837625742,82.61665357276797]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[4.287826269865036,-45.000482546165586]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-138.9921465050429,-1.8423309596255422]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-21.255824491381645,15.885210391134024]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-157.9392747581005,75.71163214277476]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-73.2338843587786,-4.1237724758684635]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[104.13818456232548,-36.57757187727839]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-56.15695391781628,69.78384184185416]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[62.47151901014149,5.825139814987779]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[105.97231487743556,-10.9131254022941]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[63.177881855517626,-22.38561765756458]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[178.24590135365725,68.59754270408303]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-34.85676401294768,-30.68933938164264]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-167.50994184985757,64.53059337101877]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-108.98514504544437,17.155385515652597]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-148.04009104147553,-24.630167903378606]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-168.31853373907506,68.53996183723211]},"properties":{"mapbox": 37}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-79.39349262043834,22.610792331397533]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.11431717127562,-17.222693203948438]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-95.66886300221086,20.973391821607947]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[59.1321013122797,77.73103675805032]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[64.46210349909961,-3.797665061429143]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-152.65658154152334,61.17343520745635]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-5.032834801822901,-80.51403128542006]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[149.14262588135898,14.811443709768355]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-117.67312103882432,35.601952923461795]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[58.696921449154615,76.93483407609165]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[115.12597859837115,-27.19985560979694]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.42650183849037,60.63311350531876]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[38.965260637924075,-12.436161800287664]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-124.12975067272782,46.57280751038343]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-79.01402894407511,59.47802145034075]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[3.1428339891135693,-50.61879039276391]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[150.56984103284776,-67.89576589129865]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[162.56736790761352,-81.48028237279505]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[143.31506738439202,44.763501500710845]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[87.38029123283923,82.75920418091118]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[161.4928848668933,23.620088850148022]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-46.51166198775172,17.211917680688202]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[76.91065756604075,-3.8099643727764487]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[68.9309889357537,-33.26238655485213]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.56363772414625,-57.36148780211806]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.0216147731989622,-66.06437901500612]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[112.04996204003692,87.48259470798075]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[16.097388714551926,41.1862831749022]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[41.597789945080876,82.33694112394005]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[170.73053332976997,20.212005176581442]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-51.28361273556948,12.821408319287002]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-126.20331206358969,32.627388993278146]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[68.47599014639854,-48.73019208200276]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[7.503653643652797,-61.4725673943758]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-176.48825615644455,-51.726085441187024]},"properties":{"mapbox": 50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-31.279294649139047,28.34117544349283]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[7.088211653754115,-24.105680999346077]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-33.13733744435012,-7.600142173469067]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[118.55583686381578,-53.2780737709254]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-141.7861074116081,10.011035855859518]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[22.935774559155107,44.0105700166896]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-89.36553846113384,4.018968511372805]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-3.784685432910919,6.479591270908713]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-42.49874101951718,-19.924558368511498]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[86.19898351840675,74.238501470536]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-167.01308970339596,-19.07945587299764]},"properties":{"mapbox": 28}},{"type":"Feature","geometry":{"type":"Point","coordinates":[7.472661640495062,55.85181953385472]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[10.16387709416449,9.010712709277868]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-111.77245139144361,-63.788525252602994]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[132.7932282909751,59.112535561434925]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[32.11806700564921,42.33946813736111]},"properties":{"mapbox": 93}},{"type":"Feature","geometry":{"type":"Point","coordinates":[84.23201076686382,74.00251803919673]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-159.94870654307306,83.00069867167622]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-74.33175626210868,40.2495814813301]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-12.03122053295374,53.76494717784226]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[38.51891281083226,60.39851380046457]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-177.92527955956757,-27.078501577489078]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[27.8085361327976,-89.31377589236945]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[31.712982952594757,-62.790777194313705]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-110.4998944234103,39.87759333103895]},"properties":{"mapbox": 78}},{"type":"Feature","geometry":{"type":"Point","coordinates":[6.665234751999378,74.6124066459015]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.03034416772425,44.22287402674556]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-146.78715887479484,85.84780478384346]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-20.139846755191684,-31.440163403749466]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[53.40938273817301,-33.084354805760086]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[168.71282072737813,-71.07424995861948]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-48.08681420981884,36.95648339577019]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-116.24423461966217,-49.2674556048587]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-177.43650136515498,89.10378319211304]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[117.56597299128771,-60.207746461965144]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[47.808708446100354,-62.97403304837644]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[8.761591063812375,87.67553203739226]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[173.6599436122924,-47.94121703132987]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[145.28842580504715,71.71248435974121]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[22.99990575760603,-56.52551684528589]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-113.92999400384724,-6.061111846938729]},"properties":{"mapbox": 62}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-112.70329082384706,40.00028905458748]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-157.0024934783578,-25.5547647876665]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-157.7265284769237,-8.688656771555543]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-65.3096076194197,-16.137895099818707]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-100.8437733259052,33.71473289094865]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-135.32863640226424,15.69878248963505]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-175.34784416668117,-81.23251222539693]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-176.63457275368273,27.68016146030277]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-3.8195206224918365,21.758858459070325]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-7.554967403411865,-87.93070897925645]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-148.34330152720213,15.5256537348032]},"properties":{"mapbox": 62}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-22.70371813327074,-88.98335041478276]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-76.80486177094281,87.56499422248453]},"properties":{"mapbox": 56}},{"type":"Feature","geometry":{"type":"Point","coordinates":[94.73564176820219,-23.18279926199466]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.16504757292569,10.985643323510885]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[22.098287483677268,-74.52641573734581]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[97.66668090596795,41.98179124854505]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[13.54695713147521,-38.86961947660893]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[84.5068723987788,-89.03763505630195]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[105.55773354135454,-21.485011079348624]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-6.420687530189753,28.988473741337657]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-57.10427041165531,-84.32162466924638]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-27.326627410948277,-25.176213565282524]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[50.39919488132,5.478498330339789]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[55.15531028620899,-82.91263449937105]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[17.161540472880006,10.206110067665577]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-6.007457105442882,21.947381501086056]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-22.560217771679163,7.8930790442973375]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[65.83445104770362,-76.19783260859549]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-79.10166140645742,-57.76479174848646]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-52.24335338920355,28.128394335508347]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-167.75034346617758,32.644973010756075]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-60.84821074269712,68.28051251359284]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-124.8287599068135,-4.834600402973592]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.214657980948687,-7.677580271847546]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-159.34292058460414,0.939008267596364]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[34.17610913515091,-53.271518871188164]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[35.44008119031787,-45.07839510217309]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-60.85252775810659,46.61097060889006]},"properties":{"mapbox": 24}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-96.09791326336563,33.897040290758014]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[10.84976764395833,-23.576332926750183]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-99.40655621699989,40.975414481945336]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-159.6056765690446,-54.53383868560195]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-111.82251306250691,6.798568242229521]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[51.06023283675313,-32.82271181233227]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-16.16575051099062,-88.20602964609861]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-108.40289345942438,74.52982507646084]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-21.357374098151922,33.79067603498697]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[77.00604454614222,-45.98769270814955]},"properties":{"mapbox": 39}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-153.91111800447106,-53.52022921666503]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-151.2720881961286,-85.24132933001965]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[160.26194446720183,74.5986429322511]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[42.970063062384725,38.725563855841756]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[111.6670954786241,23.52170533966273]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-73.96401124075055,-33.71974874753505]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-13.79294553771615,-7.111376929096878]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-9.01755946688354,53.93363012932241]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[42.33486316166818,-28.878679485060275]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.18439082615077,-54.9345934856683]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[71.76295515149832,-70.35853689070791]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-156.8277490697801,-70.29983284883201]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[87.3418350610882,59.3576778145507]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-47.04308554530144,69.09951736684889]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-88.75740104354918,1.9157659448683262]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[147.92443921789527,55.991847859695554]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.52962773293257,26.396703282371163]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-94.97377046383917,56.86219673138112]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[99.80871565639973,32.0507845049724]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-67.59248885326087,47.07945814356208]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[146.41556630842388,-3.0061532696709037]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-21.348323486745358,62.35960899386555]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.7372110709548,-78.70617899112403]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.8734134323895,-58.981731394305825]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[118.48579172976315,-0.04956013988703489]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-23.929348513484,54.68587295617908]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-178.6426289845258,-73.14786633476615]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[30.740530053153634,58.38801684323698]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[144.24143322743475,-74.4166634324938]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-173.21241904981434,-23.111277068965137]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-107.08533606491983,77.8691914351657]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-3.0583822913467884,-8.07470298372209]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.1726858150214,66.06418832670897]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-72.12944003753364,1.034755501896143]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-28.326070625334978,44.35426794458181]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-156.58604733645916,9.969834652729332]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-46.83521627448499,-67.70592160988599]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-66.11459956504405,19.916619365103543]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-57.6163783762604,-74.36286498326808]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-21.96260266005993,1.3756098272278905]},"properties":{"mapbox": 78}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-167.64215279370546,-51.04974158573896]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-176.17273692972958,-68.08571871835738]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-14.52274871058762,-31.73920582048595]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[14.299073182046413,-23.6265701148659]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[154.40124157816172,0.8706196490675211]},"properties":{"mapbox": 39}},{"type":"Feature","geometry":{"type":"Point","coordinates":[132.24152429960668,48.12327150721103]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-174.7616035491228,77.95839596539736]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-49.875975362956524,-82.87523021455854]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-46.84728378430009,-13.968596700578928]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.8018254544585943,-77.04069994390011]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[47.43022277019918,69.33753441553563]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[30.34634574316442,84.55966786947101]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[112.62707585468888,-0.7477007573470473]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[38.783310893923044,-65.76383171137422]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[4.004682647064328,54.02404320426285]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[118.39305385015905,78.25564247556031]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[172.90896465070546,-36.173745822161436]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-41.62649025209248,28.12089948914945]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-105.92365097254515,-67.9277648543939]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[66.90812934190035,19.56935254856944]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-168.35342357866466,-40.26683282107115]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[62.108105421066284,82.71303778979927]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-111.95974258705974,50.35987776238471]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[103.6906898394227,53.36193860974163]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.43049778789282,-44.569205422885716]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[159.96820813044906,-54.039471773430705]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[45.07046598941088,-85.21366498433053]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[138.77374218776822,-61.99772702995688]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.1855802796781,71.63413338828832]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[87.69008344039321,20.79491708893329]},"properties":{"mapbox": 37}},{"type":"Feature","geometry":{"type":"Point","coordinates":[65.77875749208033,-34.482794841751456]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-159.52017948031425,7.437892626039684]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[25.50886963494122,-48.584610619582236]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-84.51872759498656,-35.723060527816415]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[148.35694441571832,50.37374516017735]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-41.84343854896724,24.71985979937017]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-172.19425158575177,-70.63582307193428]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[77.11480719968677,28.662969111464918]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-98.62663102336228,-17.722317832522094]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-67.42590799927711,-81.14906660746783]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-15.245613427832723,-0.23437766823917627]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[53.54975648224354,20.513179572299123]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.46731761842966,-11.566166351549327]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-52.09023529663682,6.282507749274373]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-83.07940570637584,-11.092898910865188]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-34.011048171669245,-63.91121062450111]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-42.468092339113355,31.389638339169323]},"properties":{"mapbox": 62}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-178.01955727860332,11.634456650353968]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-64.60163025185466,-16.182731157168746]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[93.32587975077331,9.498496516607702]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[138.447703756392,63.300821115262806]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[107.98797860741615,64.21587327495217]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[44.89903402514756,11.920283697545528]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[97.09358906373382,66.50570872239769]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[135.55744014680386,-39.8464760184288]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[24.461368694901466,-57.544735819101334]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[86.75982713699341,-37.044484368525445]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[154.97563261538744,46.89816097263247]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[63.62180722877383,48.797338963486254]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-81.6332977451384,-69.5285688014701]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-125.86669416166842,11.151868575252593]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[24.033637242391706,1.986960656940937]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-90.83612258546054,41.83964733034372]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[141.40925436280668,-56.18986068293452]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-74.97196728363633,69.41084241494536]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[86.00875438190997,-37.44377627503127]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[0.16310655511915684,30.11453411076218]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[59.132625265046954,80.28071338310838]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-26.065974170342088,-6.162758809514344]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.86116070114076,-55.637453868985176]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[45.68473267368972,-44.569785576313734]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[151.67410547845066,-58.36482801474631]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[90.00907181762159,-75.99146893713623]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-104.12095547653735,-15.194700784049928]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[117.50006223097444,28.45345518551767]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-155.98592184484005,76.06090474873781]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.13844445347786,1.7793576791882515]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-145.20926292054355,78.05272841826081]},"properties":{"mapbox": 93}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-128.45609254203737,-6.3851268868893385]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-81.19094272144139,-29.85254536382854]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.57452034205198,-38.7693885108456]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[48.1255017220974,-76.26653064507991]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-73.45806020312011,55.7819620706141]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-102.19109049998224,-2.3640777356922626]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.548731230199337,-42.93387838639319]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-157.32786169275641,50.21063269581646]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[70.70466636680067,-7.382422029040754]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-102.35983388498425,44.1893474617973]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-79.82309818267822,61.13497647922486]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[49.999863831326365,15.985758653841913]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-24.133144933730364,2.1917387237772346]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[123.59778338111937,-57.69319223240018]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[94.0684381686151,-66.65535859297961]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.03033162280917,89.06510858796537]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.44523193873465,-3.16591611597687]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-155.92443882487714,79.3146219290793]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-138.58794959262013,-83.43364333268255]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-28.40943267568946,-12.023972407914698]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[153.57163391076028,-14.328520400449634]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-131.6473619453609,-31.539645195007324]},"properties":{"mapbox": 22}},{"type":"Feature","geometry":{"type":"Point","coordinates":[8.759269108995795,-83.55959844309837]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.3498440142721,-65.3848987352103]},"properties":{"mapbox": 18}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-173.8281673192978,-61.68116744142026]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[64.335723426193,63.000016110017896]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-154.10092351958156,20.27713805437088]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[107.18762369826436,-75.27225931640714]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-80.6641783285886,89.01925991289318]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.12357725948095,-26.60714873112738]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-62.47553293593228,-43.975366819649935]},"properties":{"mapbox": 70}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-56.76539660431445,-52.99163174349815]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[168.49611894227564,75.842468412593]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[155.85793999023736,58.690635985694826]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[60.55452469736338,-52.98390756826848]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.33582170680165,-60.806593000888824]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[32.24343739449978,83.23412289842963]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-128.7367181573063,-44.541783523745835]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[26.811355277895927,-48.45495829358697]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[53.918052446097136,-50.31594221480191]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[46.152025954797864,82.9171155905351]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-148.16632015630603,-80.6247699726373]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[54.16946595534682,89.37623892445117]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[39.428468411788344,71.39732718933374]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-29.818561356514692,-87.24294427782297]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[99.59268576465547,14.529108982533216]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-30.27795326896012,51.272379979491234]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-28.50434297695756,22.147771390154958]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[98.66102700121701,-26.98136743158102]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[18.10657256282866,18.187531353905797]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[88.62086964771152,27.71508181001991]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.33975077793002,-74.81050299480557]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[146.61559757776558,-30.2143995417282]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-31.76683575846255,-62.23036696203053]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-171.3705331273377,53.36500286590308]},"properties":{"mapbox": 56}},{"type":"Feature","geometry":{"type":"Point","coordinates":[99.11290822550654,-42.837288728915155]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[144.2002857942134,35.85235783830285]},"properties":{"mapbox": 80}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-94.01839108206332,-27.737779919989407]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-19.553628768771887,87.03391993883997]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[178.0183859076351,21.385139278136194]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[71.92996750585735,16.57038039062172]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.1935795713216,58.11467200051993]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-147.9172930587083,10.932626193389297]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-92.24035447463393,26.318431729450822]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.07592094317079,47.604615851305425]},"properties":{"mapbox": 24}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-127.08934234455228,62.0113266678527]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[16.953506162390113,-56.5413715923205]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[2.1253615524619818,-57.9912473866716]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[19.528056671842933,-1.8807247700169683]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[57.66796647571027,41.272982941009104]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[25.238397186622024,21.62111126817763]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[21.714930161833763,65.44598327949643]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-10.444665905088186,-14.791977368295193]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[138.9792958740145,64.79380751028657]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-107.37737297080457,-37.59859719313681]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-15.222228253260255,32.659749845042825]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-136.06521433219314,-49.82728966511786]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-139.78533430024981,-80.49050428904593]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-65.04433865658939,83.0693593248725]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.45413375645876,4.437992465682328]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.07782488316298,-63.647979008965194]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.22275203093886,60.31969440635294]},"properties":{"mapbox": 39}},{"type":"Feature","geometry":{"type":"Point","coordinates":[118.87979784049094,46.770676667802036]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[15.858520232141018,-18.45026441384107]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[4.697578875347972,-89.0603848407045]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[0.29192584566771984,-39.75866707507521]},"properties":{"mapbox": 28}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-41.20185096748173,-68.3810604410246]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[82.81948900781572,-75.26050860062242]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-45.95500378869474,68.84790445212275]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[36.90243864431977,19.058164414018393]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-126.76482019945979,-44.839791068807244]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-13.311854638159275,36.16773976944387]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.62683400139213,-54.12948612123728]},"properties":{"mapbox": 60}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-3.5442145820707083,-11.6369563434273]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-86.40065554529428,-89.81849625706673]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-9.042281722649932,-40.06876312661916]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[133.63959029316902,54.72414292395115]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-169.43282327614725,-17.5564864045009]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[175.429325401783,63.34874477237463]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[24.69895239919424,-30.67570684477687]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-9.021967090666294,-2.826137817464769]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[80.16331896185875,2.071308419108391]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[156.4887184370309,-0.4145095031708479]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[56.957034738734365,-2.7943947119638324]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.7041953895241,40.22421536967158]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-140.48747568391263,50.707125761546195]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[143.19000905379653,7.287166477181017]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-10.800317181274295,-53.24537890031934]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-153.7102773785591,-48.76605811994523]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[80.65161150880158,-43.97842189762741]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.3912587761879,83.58667865395546]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[150.17928080633283,6.153912632726133]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-64.38352927565575,9.823483773507178]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[58.912744307890534,47.091200393624604]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-136.8062863033265,18.77691979520023]},"properties":{"mapbox": 78}},{"type":"Feature","geometry":{"type":"Point","coordinates":[26.39245541766286,-65.10943888686597]},"properties":{"mapbox": 50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[130.17711753956974,-70.88348852936178]},"properties":{"mapbox": 62}},{"type":"Feature","geometry":{"type":"Point","coordinates":[127.53033262677491,36.75111402757466]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-35.52253481000662,7.647080873139203]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.69959722273052,-51.24652113765478]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-120.66181140951812,74.7534735314548]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-174.26890907809138,-77.67188294325024]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-48.27908635139465,78.52658380754292]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-116.65499053895473,-21.797942728735507]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[8.694093273952603,36.89580378122628]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-148.6999091785401,78.27982326038182]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-178.54933228343725,66.6313630901277]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-3.974916497245431,67.34660467598587]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-37.00837626121938,-84.84913899097592]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[145.97335637547076,34.95663187466562]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-56.28863143734634,-34.78939819615334]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[84.26847699098289,43.814452551305294]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[0.9951109439134598,-58.606802369467914]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.47845210321248,-47.21297385636717]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[88.57873197644949,18.16271819639951]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-75.12000326067209,-4.942738856188953]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[71.3886886741966,80.58788360562176]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[59.52814548276365,-72.08766542840749]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-37.485461784526706,9.892063331790268]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[55.82209210842848,-78.70944311376661]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-126.47003570571542,67.87966396193951]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-38.11043333262205,-42.36012026667595]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-68.53293872438371,33.946018600836396]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[25.31429372727871,2.216475773602724]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-39.22215071506798,11.108307824470103]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-117.8312218375504,19.109560777433217]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-62.75334340520203,-20.642561917193234]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[162.2659510653466,51.33885647635907]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-85.78651492483914,-31.16941594518721]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-107.61159779503942,-62.45351070072502]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-43.02660595625639,2.7774219028651714]},"properties":{"mapbox": 78}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-1.0773403476923704,38.77470628358424]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[106.1652319226414,-45.1364218024537]},"properties":{"mapbox": 13}},{"type":"Feature","geometry":{"type":"Point","coordinates":[10.962674310430884,58.15609788056463]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-113.2804127689451,8.134800517000258]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-126.32169186137617,7.1781254187226295]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-141.00995030254126,83.41457965783775]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-32.186284800991416,64.95155287440866]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-93.25948778539896,66.35963486507535]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[162.5218812469393,-85.55437384173274]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-35.17443101853132,75.40759117808193]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[79.57131528295577,3.550692368298769]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[158.92351563088596,-36.40004706569016]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[3.597600096836686,78.08964542113245]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[137.93049540370703,44.72675796132535]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-139.01084736920893,2.4230836518108845]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-16.42126075923443,82.77291106525809]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-85.56778874248266,-45.06074319127947]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[147.66723959706724,74.23324228730053]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[9.374368200078607,-12.979612885974348]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[153.0515046324581,-52.29332311078906]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[162.6496712397784,-44.30713335983455]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[100.44096311554313,15.496364515274763]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.89768223650753,1.9159417133778334]},"properties":{"mapbox": 22}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-153.06688802316785,-51.16702142637223]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-168.30253830179572,31.704005850479007]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[170.20371629856527,48.0165243241936]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-118.86547593399882,-45.99883854389191]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[109.96864805929363,-28.284501330927014]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-137.61558218859136,-50.10698384139687]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[61.364564979448915,-35.452584475278854]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[46.31492421962321,-27.288771467283368]},"properties":{"mapbox": 62}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-78.66735175251961,10.934291509911418]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[27.444263231009245,28.857922833412886]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[174.26482893526554,56.37111263349652]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-52.15299094095826,-37.03383679501712]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.7483246102929,11.989140948280692]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[117.9758988507092,32.22611380275339]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-118.42702779918909,4.662831611931324]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[76.23662388883531,-60.47018527518958]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[120.13247075490654,-63.97725821938366]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[111.46802946925163,-11.89390660263598]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-129.72748928703368,24.851180207915604]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.6280823033303,52.36793629825115]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[53.84586028754711,45.34744279459119]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-157.346476810053,54.07337267417461]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[80.17869346775115,-11.497598988935351]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-84.22597562894225,-15.214260583743453]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[140.411637397483,23.820633427239954]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[75.10164597071707,-4.103507841937244]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.12896228395402,-54.10661534406245]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[147.15162036940455,34.01164932176471]},"properties":{"mapbox": 93}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-25.507478322833776,60.1441810419783]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-170.80214923247695,87.06924199126661]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-99.63398943655193,-5.895955748856068]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[153.87531998567283,-82.07382136490196]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[59.5482088252902,83.31037675961852]},"properties":{"mapbox": 28}},{"type":"Feature","geometry":{"type":"Point","coordinates":[169.42099289037287,-44.332350650802255]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-81.0585052985698,84.089150656946]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[96.7931511439383,26.270965849980712]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-41.14514362066984,-88.08813147246838]},"properties":{"mapbox": 22}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-119.17482473887503,-64.68080281745642]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-91.66118399240077,55.00636550132185]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[26.533783692866564,-10.111068841069937]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-169.00810243561864,-80.94805620610714]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[83.92337643541396,-70.91858737170696]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-110.2635882049799,35.70818206295371]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-84.9253267608583,53.84632766246796]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[49.118056604638696,33.63964528776705]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-24.056128319352865,-10.252402815967798]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[97.68289796076715,30.469459891319275]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[74.90200244821608,-9.028823194094002]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[152.8573114424944,-57.970860209316015]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[44.18907961808145,-16.43768711015582]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[148.63756307400763,-84.64647400192916]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[65.60330590233207,-49.905973859131336]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.47866693139076,16.95708565413952]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[82.93869199231267,-27.325535165145993]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[66.65229806676507,12.038620957173407]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[99.35018842108548,-6.0751106310635805]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[164.87748512998223,5.028611663728952]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-174.6840535942465,48.577332738786936]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[101.79773608222604,17.904092436656356]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-146.83340367861092,-13.186326962895691]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[141.25340058468282,-36.80527599994093]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-97.06560029648244,-2.499908823519945]},"properties":{"mapbox": 56}},{"type":"Feature","geometry":{"type":"Point","coordinates":[20.051825875416398,-28.331027599051595]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-69.09159089438617,8.838886083103716]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.78145289979875,26.103684855625033]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[63.50223418325186,69.0458416286856]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[94.53597494401038,-85.31247370876372]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[139.0061789844185,-63.92883948516101]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[123.51005268283188,20.858707227744162]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-135.57148729451,-86.3604017905891]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-8.634117403998971,-54.98179650865495]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[128.89058919623494,17.69125864841044]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[51.721485536545515,52.81110931187868]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[45.243595289066434,-69.397656256333]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[47.58692949078977,36.74179695546627]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.0598024725914001,-48.50758038461208]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-160.79553843475878,59.84347100369632]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[10.428292946889997,79.16157390922308]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[46.70798409730196,-31.963963955640793]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[80.49403206445277,-54.75363548845053]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-155.00173721462488,36.564332325942814]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-99.90659466013312,-9.391259956173599]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[174.40395344048738,-89.39820596482605]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.71011330746114,-12.310519167222083]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[45.58134910650551,-86.06798916589469]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-118.20363834500313,-0.7904840866103768]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-91.22555846348405,77.6962536200881]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.6427268795669,-59.78692471515387]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-63.40021602809429,-24.8575499933213]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[164.02178032323718,-89.58281000610441]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-47.211816776543856,-88.26625022571534]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.34501519985497,22.663344140164554]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-148.6946810502559,74.30929938331246]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-142.7136817574501,23.76247123349458]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[48.25156210921705,15.501244836486876]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-151.281230840832,63.81557922810316]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[77.62720978818834,53.6626740032807]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-150.78787939622998,41.213500681333244]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-81.1691730748862,4.174361410550773]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-140.40619930252433,23.28936891630292]},"properties":{"mapbox": 98}},{"type":"Feature","geometry":{"type":"Point","coordinates":[64.12042116746306,-70.6528618466109]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-99.25460337661207,-6.878957943990827]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-140.83626553416252,65.52241249009967]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[134.3968651536852,-9.85090454109013]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[36.84801260009408,35.2313032373786]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-129.0603198017925,40.963220740668476]},"properties":{"mapbox": 56}},{"type":"Feature","geometry":{"type":"Point","coordinates":[150.62101397663355,49.33374434709549]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[76.06144060380757,-34.03870543465018]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[163.979467805475,46.35060925036669]},"properties":{"mapbox": 22}},{"type":"Feature","geometry":{"type":"Point","coordinates":[150.81318302080035,7.42405473254621]},"properties":{"mapbox": 28}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-29.844722198322415,3.979192692786455]},"properties":{"mapbox": 50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-52.835115334019065,25.010058763436973]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-75.71951259858906,-73.55637003667653]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-136.67117664590478,-70.3429255541414]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-73.06231426075101,70.98984100855887]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-156.91963417455554,15.910866516642272]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-12.372664399445057,-4.694070084951818]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.77768355794251,-27.85627781879157]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[44.70168672502041,-20.528464056551456]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-12.335470709949732,-23.537044641561806]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-140.60228219255805,58.972157374955714]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-123.32070540636778,-65.65118345897645]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.25051387399435,2.9149874160066247]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[21.60031191073358,86.2766702240333]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-154.44363280199468,-24.90678763948381]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[131.86044082045555,-2.055057669058442]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[144.8912670928985,70.94839916098863]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-161.79878384806216,88.69376035407186]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-74.18066591955721,-43.07885346002877]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-97.25652420893312,39.15966148953885]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[92.06954025663435,51.810072655789554]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-120.58100902475417,86.1243249848485]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-55.17589934170246,87.69860942848027]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-20.77899729833007,87.23163298331201]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-155.208152057603,19.185247686691582]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-142.62498152442276,-88.59571723267436]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[2.8595207165926695,-1.6956494143232703]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-61.74546221271157,-21.984181911684573]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[38.3966522756964,-35.05400796420872]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.4841711372137,42.794102230109274]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[31.27043003216386,48.79290644545108]},"properties":{"mapbox": 71}},{"type":"Feature","geometry":{"type":"Point","coordinates":[68.59418503940105,-20.0829499354586]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[155.70995614863932,-63.138474840670824]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[136.2470793351531,-52.26797728333622]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-18.647782830521464,81.263442998752]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-112.93791395612061,-63.559550140053034]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-176.27190364524722,-74.96294588316232]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-77.52021881751716,-3.193904464133084]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-69.39737948589027,85.30365280341357]},"properties":{"mapbox": 43}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-73.1302220094949,52.12994514964521]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-42.95680331066251,-83.01427349913865]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-124.12824913859367,77.08044554572552]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-15.888626771047711,-68.14556093886495]},"properties":{"mapbox": 80}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-18.11258959583938,85.1007583597675]},"properties":{"mapbox": 25}},{"type":"Feature","geometry":{"type":"Point","coordinates":[73.30335047096014,-18.588879550807178]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.48652151413262,34.96230235788971]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[87.45559391565621,12.473756019026041]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[124.65132314711809,41.32522470317781]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[151.07140984386206,24.874209025874734]},"properties":{"mapbox": 75}},{"type":"Feature","geometry":{"type":"Point","coordinates":[102.68346453085542,-8.390642311424017]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[148.34359036758542,-42.38129588775337]},"properties":{"mapbox": 76}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-99.5635505206883,70.45818820595741]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-96.98967810720205,10.043996097519994]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[159.94566743262112,-59.565283516421914]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[47.37261042930186,62.13936241809279]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[103.2947247941047,43.08889305219054]},"properties":{"mapbox": 30}},{"type":"Feature","geometry":{"type":"Point","coordinates":[146.88465625047684,-58.113972656428814]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-137.05790324136615,20.781153165735304]},"properties":{"mapbox": 66}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-138.77296476624906,44.03101716656238]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-51.44303695298731,4.003277798183262]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[124.53414648771286,14.820579690858722]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-83.0254449415952,-46.82651187758893]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[46.23384213075042,-63.285608841106296]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-174.23582612536848,4.970098645426333]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.85280626267195,-6.949295564554632]},"properties":{"mapbox": 78}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-163.1239470653236,31.5959233045578]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[116.5824761800468,-59.94564942084253]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-136.6917859017849,-37.11364797782153]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[103.25530093163252,24.097403325140476]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[122.77328565716743,-19.82821409124881]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[45.46220228075981,-62.11868908256292]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[5.600327868014574,29.645729823969305]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[176.25345189124346,44.75449895951897]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[155.4346466716379,-34.477172805927694]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[66.23376390896738,-24.263595174998045]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-78.48930604755878,-2.1970386430621147]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[7.033643200993538,5.49671224784106]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[25.701569924131036,87.46262247674167]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[9.385570446029305,-88.91452769748867]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-78.43776396475732,71.47421572823077]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-132.13995876722038,-24.565173578448594]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[85.15281915664673,26.98189042042941]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[33.14551834948361,58.590917866677046]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.777206981554627,-40.59781854506582]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-152.95283705927432,40.74522692710161]},"properties":{"mapbox": 55}},{"type":"Feature","geometry":{"type":"Point","coordinates":[33.83132725954056,35.963861416094005]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[30.65096413716674,-44.38926624599844]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-67.66422185115516,-86.67946358677]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[146.24046784825623,-26.32830686867237]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[161.002056626603,26.446620197966695]},"properties":{"mapbox": 18}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-79.14230517111719,-20.463960911147296]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-157.82514919526875,-10.657458454370499]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-35.3489267360419,-69.7335834056139]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-81.21029569767416,-18.07863819412887]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-151.52670501731336,-44.022110723890364]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.007898222655058,54.24188781995326]},"properties":{"mapbox": 24}},{"type":"Feature","geometry":{"type":"Point","coordinates":[130.3710228111595,-81.0157336620614]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-81.64349206723273,-26.099831108003855]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[148.72055573388934,-54.5143882278353]},"properties":{"mapbox": 46}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-43.217031145468354,48.402493530884385]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.64904686063528,0.4130655527114868]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-59.510142747312784,65.23696623276919]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-101.7116868775338,46.655783825553954]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[58.53164926171303,14.368595536798239]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-103.56381808407605,39.015411073341966]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-89.27093988284469,-44.17215290945023]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-91.35125365108252,41.45888156723231]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-131.08146680518985,-66.59091998822987]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-117.08627759478986,-16.947918450459838]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[153.62486980855465,-53.81671339273453]},"properties":{"mapbox": 81}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-106.19803701527417,-80.1888371212408]},"properties":{"mapbox": 10}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-174.25188459455967,29.692986072041094]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[32.50364159233868,-2.496404224075377]},"properties":{"mapbox": 24}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-103.06340957991779,-78.55048308614641]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-108.9317402523011,-60.855275806970894]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[55.77836690470576,40.05867382977158]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-162.11817325092852,49.16000903584063]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[145.13850834220648,3.7004789896309376]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-62.081177132204175,-21.964824576862156]},"properties":{"mapbox": 11}},{"type":"Feature","geometry":{"type":"Point","coordinates":[20.93774601817131,-53.444322030991316]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-171.9756575860083,-64.89817209541798]},"properties":{"mapbox": 41}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-116.13323274068534,26.10062365885824]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[156.13474253565073,34.234340228140354]},"properties":{"mapbox": 29}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-172.49762929044664,51.66065710131079]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[145.3119023423642,-9.575124210678041]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-2.450918024405837,88.25738921295851]},"properties":{"mapbox": 34}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-149.6307476889342,20.590436118654907]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[31.362647144123912,-4.1990955686196685]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-56.86572044156492,84.52292944304645]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[123.03662016056478,66.05640883091837]},"properties":{"mapbox": 5}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-18.58963832259178,76.13934129942209]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[144.3702707067132,4.861797667108476]},"properties":{"mapbox": 95}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-82.99937093630433,21.335826530121267]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-7.625324930995703,-20.497307181358337]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[97.59205766022205,-55.40064494591206]},"properties":{"mapbox": 59}},{"type":"Feature","geometry":{"type":"Point","coordinates":[21.26893705688417,68.53056010790169]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[32.33690533787012,72.49751069582999]},"properties":{"mapbox": 63}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-100.7882856298238,70.50066102761775]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-31.679830932989717,-1.3257330190390348]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[159.46228014305234,20.694489609450102]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-28.144683223217726,86.04833926074207]},"properties":{"mapbox": 21}},{"type":"Feature","geometry":{"type":"Point","coordinates":[140.42864402756095,22.618745206855237]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[130.53889791481197,-24.182720789685845]},"properties":{"mapbox": 50}},{"type":"Feature","geometry":{"type":"Point","coordinates":[70.73590253479779,-86.99330936651677]},"properties":{"mapbox": 18}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-98.10030215419829,88.10556780081242]},"properties":{"mapbox": 37}},{"type":"Feature","geometry":{"type":"Point","coordinates":[62.98887446522713,48.63231299445033]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[15.595593843609095,-36.780918440781534]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[51.286226296797395,32.74736411869526]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.15404026396573,77.14950484223664]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[39.96729226782918,-53.55903407558799]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-62.48722493648529,28.135843332856894]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[30.14424548484385,16.105412081815302]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-33.47911214455962,-48.06516291573644]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-109.41398383118212,31.07513818424195]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-119.61529349908233,-86.51946256868541]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-66.0242850612849,-82.31715480331331]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[165.09211147204041,-38.887647399678826]},"properties":{"mapbox": 64}},{"type":"Feature","geometry":{"type":"Point","coordinates":[144.1246804408729,-75.494344253093]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[79.13097124546766,86.41044996678829]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[100.04751775413752,88.57521191239357]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[2.6331742107868195,-30.016327667981386]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-44.79260975494981,51.192964632064104]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-63.4757920447737,23.448330452665687]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[147.41040794178843,74.48687079828233]},"properties":{"mapbox": 72}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-116.04864517226815,-82.21157119609416]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[5.459511307999492,-20.853945594280958]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.284392565488815,-79.69690054189414]},"properties":{"mapbox": 85}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-173.80448911339045,-44.92031320463866]},"properties":{"mapbox": 18}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-57.34907361678779,-23.40254321694374]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[85.30275883153081,-57.6118979556486]},"properties":{"mapbox": 83}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-145.31552751548588,7.91812433861196]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-145.0054969266057,-43.26822588220239]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.0090924669057,74.83082466758788]},"properties":{"mapbox": 54}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-65.92883471399546,-33.93926358316094]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-121.67479659430683,-25.25705936830491]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-6.830966686829925,43.81351998075843]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-24.402449699118733,70.72312654461712]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-159.88104346208274,-35.65545494668186]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[116.76082606427372,87.32783841434866]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[156.32297242060304,-42.29135069064796]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-24.906591586768627,-70.6086895102635]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[41.94615198299289,23.215975589118898]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-75.20145440474153,-36.35918679647148]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[3.9644900802522898,20.716046816669405]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[11.512293219566345,55.34615373238921]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[161.62389552220702,-39.50468803290278]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-121.57353406772017,26.769525934942067]},"properties":{"mapbox": 8}},{"type":"Feature","geometry":{"type":"Point","coordinates":[166.50501578114927,-70.0815476803109]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-72.37302795052528,44.60104311816394]},"properties":{"mapbox": 58}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-134.97790245339274,-9.214969296008348]},"properties":{"mapbox": 16}},{"type":"Feature","geometry":{"type":"Point","coordinates":[108.72965667396784,-20.58310580905527]},"properties":{"mapbox": 67}},{"type":"Feature","geometry":{"type":"Point","coordinates":[157.98479829914868,14.673859756439924]},"properties":{"mapbox": 45}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-94.03324767015874,-4.2099331598728895]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-133.3403992652893,-6.713104136288166]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[112.25320626050234,-19.00266712065786]},"properties":{"mapbox": 17}},{"type":"Feature","geometry":{"type":"Point","coordinates":[15.881055146455765,34.15173502173275]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[17.096276711672544,-19.413785682991147]},"properties":{"mapbox": 14}},{"type":"Feature","geometry":{"type":"Point","coordinates":[52.863189931958914,41.629943093284965]},"properties":{"mapbox": 77}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.475525019690394,74.3149668071419]},"properties":{"mapbox": 82}},{"type":"Feature","geometry":{"type":"Point","coordinates":[102.97935325652361,15.563243804499507]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[12.641265923157334,-47.30335277505219]},"properties":{"mapbox": 7}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-12.239387277513742,-77.80029118526727]},"properties":{"mapbox": 56}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-40.80227231606841,-15.242899786680937]},"properties":{"mapbox": 35}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-131.24569326639175,31.949481619521976]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[114.0491375606507,23.350589415058494]},"properties":{"mapbox": 65}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-176.64725842885673,-43.534041051752865]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[131.65887675248086,-64.21674298122525]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[21.92272173240781,-81.51891858782619]},"properties":{"mapbox": 49}},{"type":"Feature","geometry":{"type":"Point","coordinates":[138.82551628164947,80.91470603831112]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[143.14156810753047,-25.795541242696345]},"properties":{"mapbox": 1}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-166.800395892933,60.18528484739363]},"properties":{"mapbox": 9}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-144.54735475592315,-3.065854632295668]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-150.76181737706065,-83.66180477198213]},"properties":{"mapbox": 80}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-83.05852730758488,-15.419311146251857]},"properties":{"mapbox": 39}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.13011137209833,74.085004767403]},"properties":{"mapbox": 47}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-76.42930540256202,-54.16420857422054]},"properties":{"mapbox": 12}},{"type":"Feature","geometry":{"type":"Point","coordinates":[142.2087097261101,44.53137613367289]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-64.55603831447661,34.49843735899776]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[42.12144163437188,-11.64163616951555]},"properties":{"mapbox": 87}},{"type":"Feature","geometry":{"type":"Point","coordinates":[85.58279578574002,-14.729487095028162]},"properties":{"mapbox": 88}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-45.583537705242634,28.341183657757938]},"properties":{"mapbox": 61}},{"type":"Feature","geometry":{"type":"Point","coordinates":[3.103248104453087,79.1934456769377]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[169.1800141800195,86.2348373606801]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-178.94530529156327,55.29307767748833]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.3650418817996979,67.20791833475232]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.72778706811368,-48.864370845258236]},"properties":{"mapbox": 74}},{"type":"Feature","geometry":{"type":"Point","coordinates":[178.343421863392,64.60342163220048]},"properties":{"mapbox": 32}},{"type":"Feature","geometry":{"type":"Point","coordinates":[177.611748771742,81.19969087187201]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[52.74806583300233,-84.27109310869128]},"properties":{"mapbox": 15}},{"type":"Feature","geometry":{"type":"Point","coordinates":[10.081364549696445,-62.88397922180593]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[70.66772446967661,-30.233216201886535]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-35.57482288219035,27.087600510567427]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-45.83435098640621,-58.47435358911753]},"properties":{"mapbox": 33}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-20.12691012583673,78.88310491573066]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-19.84980046749115,-73.40256605297327]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[99.99531182460487,82.13316347915679]},"properties":{"mapbox": 90}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-40.85276967845857,67.46957691852003]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[49.27813838236034,52.89751499891281]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[16.61237770691514,69.2438368499279]},"properties":{"mapbox": 73}},{"type":"Feature","geometry":{"type":"Point","coordinates":[108.57693596743047,36.01974913850427]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-100.97691575065255,-76.26019690185785]},"properties":{"mapbox": 91}},{"type":"Feature","geometry":{"type":"Point","coordinates":[85.72310507297516,12.150512491352856]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[159.7318111360073,-36.321309520863]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-65.63492445275187,75.05002991762012]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-118.2563142478466,-47.561271605081856]},"properties":{"mapbox": 79}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-124.13570182397962,-13.409972484223545]},"properties":{"mapbox": 100}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-163.29969746991992,43.76724378671497]},"properties":{"mapbox": 68}},{"type":"Feature","geometry":{"type":"Point","coordinates":[25.569858038797975,-26.38374963775277]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[179.44557675160468,78.71839796192944]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[133.20120753720403,32.78069098480046]},"properties":{"mapbox": 2}},{"type":"Feature","geometry":{"type":"Point","coordinates":[1.894548749551177,19.006769182160497]},"properties":{"mapbox": 89}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-143.3043299149722,38.19644402246922]},"properties":{"mapbox": 27}},{"type":"Feature","geometry":{"type":"Point","coordinates":[127.04680577851832,-29.405142883770168]},"properties":{"mapbox": 99}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-46.50432136841118,-77.19790186733007]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[72.4947122298181,-61.52153761591762]},"properties":{"mapbox": 40}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-165.14324208721519,87.08859681151807]},"properties":{"mapbox": 84}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-115.42123725637794,-67.04487129114568]},"properties":{"mapbox": 86}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-114.85377822071314,19.37291443347931]},"properties":{"mapbox": 23}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-145.76720614917576,3.752908087335527]},"properties":{"mapbox": 96}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-36.03865125216544,-39.25180781632662]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[57.959803557023406,-9.167421525344253]},"properties":{"mapbox": 52}},{"type":"Feature","geometry":{"type":"Point","coordinates":[169.3040323164314,85.74065003078431]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[113.3064625505358,52.742472756654024]},"properties":{"mapbox": 92}},{"type":"Feature","geometry":{"type":"Point","coordinates":[73.00019471906126,-87.96791079919785]},"properties":{"mapbox": 6}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-91.15831162780523,-14.161390447989106]},"properties":{"mapbox": 4}},{"type":"Feature","geometry":{"type":"Point","coordinates":[62.7123543061316,30.70386136416346]},"properties":{"mapbox": 53}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-42.837291955947876,-64.84413576778024]},"properties":{"mapbox": 26}},{"type":"Feature","geometry":{"type":"Point","coordinates":[27.029835870489478,-26.582003817893565]},"properties":{"mapbox": 48}},{"type":"Feature","geometry":{"type":"Point","coordinates":[152.738849921152,-36.81975812651217]},"properties":{"mapbox": 31}},{"type":"Feature","geometry":{"type":"Point","coordinates":[124.25105608999729,-36.163670984096825]},"properties":{"mapbox": 19}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-13.68607610464096,45.78369679860771]},"properties":{"mapbox": 69}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-56.30010978318751,47.8517619241029]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-84.61627434007823,37.964905430562794]},"properties":{"mapbox": 20}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-8.222548104822636,-36.19494575075805]},"properties":{"mapbox": 51}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-175.1437535416335,11.454095025546849]},"properties":{"mapbox": 44}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-108.27845404855907,-3.3510214369744062]},"properties":{"mapbox": 57}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-139.8142277263105,41.52774778660387]},"properties":{"mapbox": 42}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-155.18113357946277,77.3847569944337]},"properties":{"mapbox": 36}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-143.1226884573698,-89.31461127474904]},"properties":{"mapbox": 97}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-15.941828219220042,-76.42533141653985]},"properties":{"mapbox": 94}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-153.1626615766436,-76.40048192348331]},"properties":{"mapbox": 3}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-8.032905869185925,18.317440166138113]},"properties":{"mapbox": 38}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-140.5970814730972,-59.16737722232938]},"properties":{"mapbox": 39}},{"type":"Feature","geometry":{"type":"Point","coordinates":[118.17669169977307,69.35299408156425]},"properties":{"mapbox": 82}}]} diff --git a/debug/route.json b/debug/route.json deleted file mode 100644 index 6536448c169..00000000000 --- a/debug/route.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "Feature", - "geometry": { - "type": "LineString", - "coordinates": [[-77.066104,38.910203],[-77.066106,38.910321],[-77.066112,38.910758],[-77.065249,38.910775],[-77.065178,38.910793],[-77.065139,38.910804],[-77.064904,38.91036],[-77.064855,38.910268],[-77.064621,38.909825],[-77.06459,38.909766],[-77.064342,38.909298],[-77.064281,38.909182],[-77.064201,38.909226],[-77.064175,38.909235],[-77.064149,38.909241],[-77.064122,38.909244],[-77.06336,38.90926],[-77.061442,38.909288],[-77.060801,38.909297],[-77.059237,38.909315],[-77.058186,38.90933],[-77.057113,38.909343],[-77.055623,38.909368],[-77.054762,38.909377],[-77.053951,38.909389],[-77.053904,38.909393],[-77.053858,38.909399],[-77.053813,38.909408],[-77.052833,38.909635],[-77.052799,38.909642],[-77.052772,38.909645],[-77.052692,38.90965],[-77.052443,38.909649],[-77.05096,38.909639],[-77.050327,38.909634],[-77.049545,38.909631],[-77.049533,38.909635],[-77.049341,38.909635],[-77.048903,38.909635],[-77.048797,38.909634],[-77.04773,38.909639],[-77.046632,38.90964],[-77.045758,38.909641],[-77.044877,38.909643],[-77.044578,38.909641],[-77.044493,38.909635],[-77.04442,38.909626],[-77.044345,38.909608],[-77.044283,38.909578],[-77.044249,38.909541],[-77.044212,38.909496],[-77.044199,38.909458],[-77.044182,38.909421],[-77.044162,38.909385],[-77.04414,38.90935],[-77.044113,38.909313],[-77.044083,38.90928],[-77.044051,38.909247],[-77.044018,38.909165],[-77.044018,38.909117],[-77.044029,38.909071],[-77.044037,38.909038],[-77.044058,38.908981],[-77.043996,38.909021],[-77.043919,38.909052],[-77.043854,38.909067],[-77.043798,38.909073],[-77.043755,38.909075],[-77.043689,38.909056],[-77.043656,38.909048],[-77.04359,38.909036],[-77.043527,38.909028],[-77.043486,38.909026],[-77.043444,38.909025],[-77.043402,38.909026],[-77.043344,38.909029],[-77.043286,38.909037],[-77.043263,38.909041],[-77.04323,38.909047],[-77.043194,38.909056],[-77.043022,38.909118],[-77.042995,38.909132],[-77.04296,38.909151],[-77.042938,38.909165],[-77.042915,38.90918],[-77.042875,38.909209],[-77.04284,38.909239],[-77.042823,38.909255],[-77.042791,38.909288],[-77.042774,38.909307],[-77.042712,38.909315],[-77.042637,38.909322],[-77.042551,38.909317],[-77.042422,38.909299],[-77.041676,38.90903],[-77.039935,38.908425],[-77.038503,38.907925],[-77.037656,38.907632],[-77.037477,38.907534],[-77.037317,38.907438],[-77.037238,38.90739],[-77.037121,38.907315],[-77.036997,38.907233],[-77.036875,38.907137],[-77.036833,38.907094],[-77.036817,38.907081],[-77.036795,38.907068],[-77.036774,38.907058],[-77.036767,38.907052],[-77.036712,38.907034],[-77.03669,38.907028],[-77.036643,38.907022],[-77.03661,38.907021],[-77.03653,38.907021],[-77.036474,38.907021],[-77.03643,38.907025],[-77.03639,38.907033],[-77.036295,38.907058],[-77.036005,38.906995],[-77.035439,38.906869],[-77.035142,38.906757],[-77.035049,38.906725],[-77.0348,38.906641],[-77.034673,38.906594],[-77.034568,38.906558],[-77.034059,38.906382],[-77.033931,38.906338],[-77.032623,38.905883],[-77.03129,38.905426],[-77.030031,38.904982],[-77.029623,38.904835],[-77.028082,38.904304],[-77.027679,38.904167],[-77.02705,38.903949],[-77.026409,38.903729],[-77.025984,38.903579],[-77.024321,38.902997],[-77.024139,38.902954],[-77.023952,38.902954],[-77.023188,38.902956],[-77.022699,38.902958],[-77.021917,38.90296],[-77.021918,38.902902],[-77.021918,38.902521],[-77.019907,38.902521],[-77.018928,38.902521],[-77.016176,38.902519],[-77.015177,38.902518],[-77.013722,38.902514],[-77.012171,38.902516],[-77.011237,38.902516],[-77.009846,38.902515],[-77.009117,38.902514],[-77.009126,38.901346],[-77.00912,38.900203],[-77.009062,38.900203],[-77.008975,38.900203],[-77.008004,38.900198],[-77.00631,38.900193],[-77.005531,38.900197],[-77.002929,38.9002],[-77.002038,38.900203],[-77.001892,38.900203],[-77.000571,38.900205],[-76.999507,38.900204],[-76.998442,38.900204],[-76.998369,38.900204],[-76.996167,38.900205],[-76.994961,38.900203],[-76.994962,38.899748],[-76.994961,38.899626],[-76.994961,38.899367],[-76.994961,38.898908],[-76.994962,38.898447]] - }, - "properties": { - "name": "P Street Northwest - Massachusetts Avenue Northwest" - } -} diff --git a/docs/_posts/examples/3400-01-10-live-geojson.html b/docs/_posts/examples/3400-01-10-live-geojson.html index 85f5af3c4a2..6fd5a8ab06b 100644 --- a/docs/_posts/examples/3400-01-10-live-geojson.html +++ b/docs/_posts/examples/3400-01-10-live-geojson.html @@ -14,15 +14,12 @@ }); var url = 'https://wanderdrone.appspot.com/'; -var source = new mapboxgl.GeoJSONSource({ - data: url -}); -window.setInterval(function() { - source.setData(url); -}, 2000); - map.on('load', function () { - map.addSource('drone', source); + window.setInterval(function() { + map.getSource('drone').setData(url); + }, 2000); + + map.addSource('drone', { type: 'geojson', data: url }); map.addLayer({ "id": "drone", "type": "symbol", diff --git a/docs/_posts/examples/3400-01-10-rotating-controllable-marker.html b/docs/_posts/examples/3400-01-10-rotating-controllable-marker.html index f14fe827768..1b96ebd93b1 100644 --- a/docs/_posts/examples/3400-01-10-rotating-controllable-marker.html +++ b/docs/_posts/examples/3400-01-10-rotating-controllable-marker.html @@ -21,15 +21,10 @@ "coordinates": [-74.50, 40] }; -// add the GeoJSON above to a new vector tile source -var source = new mapboxgl.GeoJSONSource({ - data: point -}); - function setPosition() { point.coordinates[0] += speed * Math.sin(direction) / 100; point.coordinates[1] += speed * Math.cos(direction) / 100; - source.setData(point); + map.getSource('drone').setData(point); map.setLayoutProperty('drone', 'icon-rotate', direction * (180 / Math.PI)); @@ -41,7 +36,8 @@ } map.on('load', function () { - map.addSource('drone', source); + // add the GeoJSON above to a new vector tile source + map.addSource('drone', { type: 'geojson', data: point }); map.addLayer({ "id": "drone-glow-strong", diff --git a/docs/_theme/section.hbs b/docs/_theme/section.hbs index 0bbf0c5a4e2..ef1cdd26075 100644 --- a/docs/_theme/section.hbs +++ b/docs/_theme/section.hbs @@ -16,7 +16,7 @@ <%= md(section.description) %> - <% if (section.kind === 'class') { %> + <% if (section.kind === 'class' && !section.interface) { %>
<%= signature(section, true) %>
<% } %> @@ -35,7 +35,7 @@ <% if (section.copyright) { %>
Copyright: <%- section.copyright %>
<% }%> <% if (section.since) { %>
Since: <%- section.since %>
<% }%> - <% if (section.params) { %> + <% if (section.params && !(section.kind === 'class' && section.interface)) { %>
Parameters
<% section.params.forEach(function(param) { %> diff --git a/documentation.yml b/documentation.yml index 7ed85668f8b..333337834a1 100644 --- a/documentation.yml +++ b/documentation.yml @@ -30,10 +30,8 @@ toc: - TouchZoomRotateHandler - name: Sources description: | - Use these methods to dynamically add content to your map after - it has been loaded, or to add GeoJSON content generated - by other JavaScript. You can also add sources to your map - by including them in the map's Mapbox style. + Sources specify the geographic features to be rendered on the map. Source + objects may be obtained from `Map#getSource`. - GeoJSONSource - VideoSource - ImageSource diff --git a/js/geo/transform.js b/js/geo/transform.js index 0ac47a8100a..8d553954894 100644 --- a/js/geo/transform.js +++ b/js/geo/transform.js @@ -127,6 +127,53 @@ Transform.prototype = { this._constrain(); }, + /** + * Return a zoom level that will cover all tiles the transform + * @param {Object} options + * @param {number} options.tileSize + * @param {boolean} options.roundZoom + * @returns {number} zoom level + * @private + */ + coveringZoomLevel: function(options) { + return (options.roundZoom ? Math.round : Math.floor)( + this.zoom + this.scaleZoom(this.tileSize / options.tileSize) + ); + }, + + /** + * Return all coordinates that could cover this transform for a covering + * zoom level. + * @param {Object} options + * @param {number} options.tileSize + * @param {number} options.minzoom + * @param {number} options.maxzoom + * @param {boolean} options.roundZoom + * @param {boolean} options.reparseOverscaled + * @returns {Array} tiles + * @private + */ + coveringTiles: function(options) { + var z = this.coveringZoomLevel(options); + var actualZ = z; + + if (z < options.minzoom) return []; + if (z > options.maxzoom) z = options.maxzoom; + + var tr = this, + tileCenter = tr.locationCoordinate(tr.center)._zoomTo(z), + centerPoint = new Point(tileCenter.column - 0.5, tileCenter.row - 0.5); + + return TileCoord.cover(z, [ + tr.pointCoordinate(new Point(0, 0))._zoomTo(z), + tr.pointCoordinate(new Point(tr.width, 0))._zoomTo(z), + tr.pointCoordinate(new Point(tr.width, tr.height))._zoomTo(z), + tr.pointCoordinate(new Point(0, tr.height))._zoomTo(z) + ], options.reparseOverscaled ? actualZ : z).sort(function(a, b) { + return centerPoint.dist(a) - centerPoint.dist(b); + }); + }, + resize: function(width, height) { this.width = width; this.height = height; diff --git a/js/mapbox-gl.js b/js/mapbox-gl.js index 08402a0e0a1..3531a866a5d 100644 --- a/js/mapbox-gl.js +++ b/js/mapbox-gl.js @@ -13,10 +13,6 @@ mapboxgl.Attribution = require('./ui/control/attribution'); mapboxgl.Popup = require('./ui/popup'); mapboxgl.Marker = require('./ui/marker'); -mapboxgl.GeoJSONSource = require('./source/geojson_source'); -mapboxgl.VideoSource = require('./source/video_source'); -mapboxgl.ImageSource = require('./source/image_source'); - mapboxgl.Style = require('./style/style'); mapboxgl.LngLat = require('./geo/lng_lat'); diff --git a/js/render/draw_background.js b/js/render/draw_background.js index 9a9340e63f4..0a04e37a4c9 100644 --- a/js/render/draw_background.js +++ b/js/render/draw_background.js @@ -1,10 +1,10 @@ 'use strict'; -var TilePyramid = require('../source/tile_pyramid'); -var pyramid = new TilePyramid({ tileSize: 512 }); var pixelsToTileUnits = require('../source/pixels_to_tile_units'); var createUniformPragmas = require('./create_uniform_pragmas'); +var tileSize = 512; + module.exports = drawBackground; function drawBackground(painter, source, layer) { @@ -64,10 +64,9 @@ function drawBackground(painter, source, layer) { // the depth and stencil buffers get into a bad state. // This can be refactored into a single draw call once earcut lands and // we don't have so much going on in the stencil buffer. - var coords = pyramid.coveringTiles(transform); + var coords = transform.coveringTiles({ tileSize: tileSize }); for (var c = 0; c < coords.length; c++) { var coord = coords[c]; - var tileSize = 512; // var pixelsToTileUnitsBound = pixelsToTileUnits.bind({coord:coord, tileSize: tileSize}); if (imagePosA && imagePosB) { var tile = {coord:coord, tileSize: tileSize}; diff --git a/js/render/draw_raster.js b/js/render/draw_raster.js index f1156283b8e..9852de44844 100644 --- a/js/render/draw_raster.js +++ b/js/render/draw_raster.js @@ -54,7 +54,7 @@ function drawRasterTile(painter, source, layer, coord) { gl.uniform1f(program.u_contrast_factor, contrastFactor(layer.paint['raster-contrast'])); gl.uniform3fv(program.u_spin_weights, spinWeights(layer.paint['raster-hue-rotate'])); - var parentTile = tile.source && tile.source._pyramid.findLoadedParent(coord, 0, {}), + var parentTile = tile.source && tile.source.findLoadedParent(coord, 0, {}), opacities = getOpacities(tile, parentTile, layer, painter.transform); var parentScaleBy, parentTL; @@ -122,7 +122,7 @@ function getOpacities(tile, parentTile, layer, transform) { var sinceTile = (now - tile.timeAdded) / fadeDuration; var sinceParent = parentTile ? (now - parentTile.timeAdded) / fadeDuration : -1; - var idealZ = tile.source._pyramid.coveringZoomLevel(transform); + var idealZ = transform.coveringZoomLevel(tile.source); var parentFurther = parentTile ? Math.abs(parentTile.coord.z - idealZ) > Math.abs(tile.coord.z - idealZ) : false; if (!parentTile || parentFurther) { diff --git a/js/render/painter.js b/js/render/painter.js index 67aed3d32ab..2574a96e30a 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -3,7 +3,7 @@ var browser = require('../util/browser'); var mat4 = require('gl-matrix').mat4; var FrameHistory = require('./frame_history'); -var TilePyramid = require('../source/tile_pyramid'); +var SourceCache = require('../source/source_cache'); var EXTENT = require('../data/bucket').EXTENT; var pixelsToTileUnits = require('../source/pixels_to_tile_units'); var util = require('../util/util'); @@ -34,7 +34,7 @@ function Painter(gl, transform) { // Within each layer there are multiple distinct z-planes that can be drawn to. // This is implemented using the WebGL depth buffer. - this.numSublayers = TilePyramid.maxUnderzooming + TilePyramid.maxOverzooming + 1; + this.numSublayers = SourceCache.maxUnderzooming + SourceCache.maxOverzooming + 1; this.depthEpsilon = 1 / Math.pow(2, 16); this.lineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); diff --git a/js/source/geojson_source.js b/js/source/geojson_source.js index e9a969279f7..3bb5844a7e3 100644 --- a/js/source/geojson_source.js +++ b/js/source/geojson_source.js @@ -1,9 +1,7 @@ 'use strict'; -var util = require('../util/util'); var Evented = require('../util/evented'); -var TilePyramid = require('./tile_pyramid'); -var Source = require('./source'); +var util = require('../util/util'); var urlResolve = require('resolve-url'); var EXTENT = require('../data/bucket').EXTENT; @@ -11,27 +9,13 @@ module.exports = GeoJSONSource; /** * A datasource containing GeoJSON. + * (See the [Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/#sources-geojson) for detailed documentation of options.) * - * @class GeoJSONSource - * @param {Object} [options] - * @param {Object|string} [options.data] A GeoJSON data object or a URL to one. The latter is preferable in the case of large GeoJSON objects. - * @param {number} [options.maxzoom=18] The maximum zoom level at which to preserve detail (1-20). - * @param {number} [options.buffer=128] The tile buffer, measured in pixels. The buffer extends each - * tile's data just past its visible edges, helping to ensure seamless rendering across tile boundaries. - * The default value, 128, is a safe value for label layers, preventing text clipping at boundaries. - * You can read more about buffers and clipping in the - * [Mapbox Vector Tile Specification](https://www.mapbox.com/vector-tiles/specification/#clipping). - * @param {number} [options.tolerance=0.375] The simplification tolerance, measured in pixels. - * This value is passed into a modified [Ramer–Douglas–Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) - * to simplify (i.e. reduce the number of points) in curves. Higher values result in greater simplification. - * @param {boolean} [options.cluster] If `true`, a collection of point features will be clustered into groups, - * according to `options.clusterRadius`. - * @param {number} [options.clusterRadius=50] The radius of each cluster when clustering points, measured in pixels. - * @param {number} [options.clusterMaxZoom] The maximum zoom level to cluster points in. By default, this value is - * one zoom level less than the map's `maxzoom`, so that at the highest zoom level features are not clustered. - + * @interface GeoJSONSource * @example - * var sourceObj = new mapboxgl.GeoJSONSource({ + * // add + * map.addSource('some id', { + * type: 'geojson', * data: { * "type": "FeatureCollection", * "features": [{ @@ -46,53 +30,80 @@ module.exports = GeoJSONSource; * }] * } * }); - * map.addSource('some id', sourceObj); // add + * + * // update + * var mySource = map.getSource('some id'); + * mySource.setData({ + * data: { + * "type": "FeatureCollection", + * "features": [{ + * "type": "Feature", + * "properties": { "name": "Null Island" }, + * "geometry": { + * "type": "Point", + * "coordinates": [ 0, 0 ] + * } + * }] + * } + * }) + * * map.removeSource('some id'); // remove */ -function GeoJSONSource(options) { +function GeoJSONSource(id, options, dispatcher) { options = options || {}; + this.id = id; + this.dispatcher = dispatcher; this._data = options.data; if (options.maxzoom !== undefined) this.maxzoom = options.maxzoom; + if (options.type) this.type = options.type; var scale = EXTENT / this.tileSize; - this.geojsonVtOptions = { - buffer: (options.buffer !== undefined ? options.buffer : 128) * scale, - tolerance: (options.tolerance !== undefined ? options.tolerance : 0.375) * scale, - extent: EXTENT, - maxZoom: this.maxzoom - }; - - this.cluster = options.cluster || false; - this.superclusterOptions = { - maxZoom: Math.min(options.clusterMaxZoom, this.maxzoom - 1) || (this.maxzoom - 1), - extent: EXTENT, - radius: (options.clusterRadius || 50) * scale, - log: false - }; - - this._pyramid = new TilePyramid({ - tileSize: this.tileSize, - minzoom: this.minzoom, - maxzoom: this.maxzoom, - reparseOverscaled: true, - load: this._loadTile.bind(this), - abort: this._abortTile.bind(this), - unload: this._unloadTile.bind(this), - add: this._addTile.bind(this), - remove: this._removeTile.bind(this), - redoPlacement: this._redoTilePlacement.bind(this) - }); + // sent to the worker, along with `url: ...` or `data: literal geojson`, + // so that it can load/parse/index the geojson data + // extending with `options.workerOptions` helps to make it easy for + // third-party sources to hack/reuse GeoJSONSource. + this.workerOptions = util.extend({ + source: this.id, + cluster: options.cluster || false, + geojsonVtOptions: { + buffer: (options.buffer !== undefined ? options.buffer : 128) * scale, + tolerance: (options.tolerance !== undefined ? options.tolerance : 0.375) * scale, + extent: EXTENT, + maxZoom: this.maxzoom + }, + superclusterOptions: { + maxZoom: Math.min(options.clusterMaxZoom, this.maxzoom - 1) || (this.maxzoom - 1), + extent: EXTENT, + radius: (options.clusterRadius || 50) * scale, + log: false + } + }, options.workerOptions); + + this._updateWorkerData(function done(err) { + if (err) { + this.fire('error', {error: err}); + return; + } + this.fire('load'); + }.bind(this)); } -GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototype */{ +GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototype */ { + // `type` is a property rather than a constant to make it easy for 3rd + // parties to use GeoJSONSource to build their own source types. + type: 'geojson', minzoom: 0, maxzoom: 18, tileSize: 512, - _dirty: true, isTileClipped: true, + reparseOverscaled: true, + + onAdd: function (map) { + this.map = map; + }, /** * Sets the GeoJSON data and re-renders the map. @@ -102,84 +113,45 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy */ setData: function(data) { this._data = data; - this._dirty = true; - this.fire('change'); - - if (this.map) - this.update(this.map.transform); + this._updateWorkerData(function (err) { + if (err) { + return this.fire('error', { error: err }); + } + this.fire('change'); + }.bind(this)); return this; }, - onAdd: function(map) { - this.map = map; - }, - - loaded: function() { - return this._loaded && this._pyramid.loaded(); - }, - - update: function(transform) { - if (this._dirty) { - this._updateData(); - } - - if (this._loaded) { - this._pyramid.update(this.used, transform); - } - }, - - reload: function() { - if (this._loaded) { - this._pyramid.reload(); - } - }, - - serialize: function() { - return { - type: 'geojson', - data: this._data - }; - }, - - getVisibleCoordinates: Source._getVisibleCoordinates, - getTile: Source._getTile, - - queryRenderedFeatures: Source._queryRenderedVectorFeatures, - querySourceFeatures: Source._querySourceFeatures, - - _updateData: function() { - this._dirty = false; - var options = { - tileSize: this.tileSize, - source: this.id, - geojsonVtOptions: this.geojsonVtOptions, - cluster: this.cluster, - superclusterOptions: this.superclusterOptions - }; - + /* + * Responsible for invoking WorkerSource's geojson.loadData target, which + * handles loading the geojson data and preparing to serve it up as tiles, + * using geojson-vt or supercluster as appropriate. + */ + _updateWorkerData: function(callback) { + var options = util.extend({}, this.workerOptions); var data = this._data; if (typeof data === 'string') { options.url = typeof window != 'undefined' ? urlResolve(window.location.href, data) : data; } else { options.data = JSON.stringify(data); } - this.workerID = this.dispatcher.send('parse geojson', options, function(err) { + + // target {this.type}.loadData rather than literally geojson.loadData, + // so that other geojson-like source types can easily reuse this + // implementation + this.workerID = this.dispatcher.send(this.type + '.loadData', options, function(err) { this._loaded = true; - if (err) { - this.fire('error', {error: err}); - } else { - this._pyramid.reload(); - this.fire('change'); - } + callback(err); }.bind(this)); }, - _loadTile: function(tile) { + loadTile: function (tile, callback) { var overscaling = tile.coord.z > this.maxzoom ? Math.pow(2, tile.coord.z - this.maxzoom) : 1; var params = { + type: this.type, uid: tile.uid, coord: tile.coord, zoom: tile.coord.z, @@ -192,7 +164,7 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy showCollisionBoxes: this.map.showCollisionBoxes }; - tile.workerID = this.dispatcher.send('load geojson tile', params, function(err, data) { + tile.workerID = this.dispatcher.send('load tile', params, function(err, data) { tile.unloadVectorData(this.map.painter); @@ -200,8 +172,7 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy return; if (err) { - this.fire('tile.error', {tile: tile}); - return; + return callback(err); } tile.loadVectorData(data, this.map.style); @@ -211,31 +182,24 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy tile.redoPlacement(this); } - this.fire('tile.load', {tile: tile}); + return callback(null); }.bind(this), this.workerID); }, - _abortTile: function(tile) { + abortTile: function(tile) { tile.aborted = true; }, - _addTile: function(tile) { - this.fire('tile.add', {tile: tile}); - }, - - _removeTile: function(tile) { - this.fire('tile.remove', {tile: tile}); - }, - - _unloadTile: function(tile) { + unloadTile: function(tile) { tile.unloadVectorData(this.map.painter); this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, function() {}, tile.workerID); }, - redoPlacement: Source.redoPlacement, - - _redoTilePlacement: function(tile) { - tile.redoPlacement(this); + serialize: function() { + return { + type: this.type, + data: this._data + }; } }); diff --git a/js/source/geojson_worker_source.js b/js/source/geojson_worker_source.js new file mode 100644 index 00000000000..205199c52a4 --- /dev/null +++ b/js/source/geojson_worker_source.js @@ -0,0 +1,136 @@ +'use strict'; + +var util = require('../util/util'); +var ajax = require('../util/ajax'); +var rewind = require('geojson-rewind'); +var GeoJSONWrapper = require('./geojson_wrapper'); +var vtpbf = require('vt-pbf'); +var supercluster = require('supercluster'); +var geojsonvt = require('geojson-vt'); + +var VectorTileWorkerSource = require('./vector_tile_worker_source'); + +module.exports = GeoJSONWorkerSource; + +/** + * The {@link WorkerSource} implementation that supports {@link GeoJSONSource}. + * This class is designed to be easily reused to support custom source types + * for data formats that can be parsed/converted into an in-memory GeoJSON + * representation. To do so, create it with + * `new GeoJSONWorkerSource(actor, styleLayers, customLoadGeoJSONFunction)`. For a full example, see [mapbox-gl-topojson](https://github.com/developmentseed/mapbox-gl-topojson). + * + * @class GeoJSONWorkerSource + * @private + * @param {Function} [loadGeoJSON] Optional method for custom loading/parsing of GeoJSON based on parameters passed from the main-thread Source. See {@link GeoJSONWorkerSource#loadGeoJSON}. + */ +function GeoJSONWorkerSource (actor, styleLayers, loadGeoJSON) { + if (loadGeoJSON) { this.loadGeoJSON = loadGeoJSON; } + VectorTileWorkerSource.call(this, actor, styleLayers); +} + +GeoJSONWorkerSource.prototype = util.inherit(VectorTileWorkerSource, /** @lends GeoJSONWorkerSource.prototype */ { + // object mapping source ids to geojson-vt-like tile indexes + _geoJSONIndexes: {}, + + /** + * See {@link VectorTileWorkerSource#loadTile}. + */ + loadVectorData: function (params, callback) { + var source = params.source, + coord = params.coord; + + if (!this._geoJSONIndexes[source]) return callback(null, null); // we couldn't load the file + + var geoJSONTile = this._geoJSONIndexes[source].getTile(Math.min(coord.z, params.maxZoom), coord.x, coord.y); + if (geoJSONTile) { + var geojsonWrapper = new GeoJSONWrapper(geoJSONTile.features); + geojsonWrapper.name = '_geojsonTileLayer'; + var pbf = vtpbf({ layers: { '_geojsonTileLayer': geojsonWrapper }}); + if (pbf.byteOffset !== 0 || pbf.byteLength !== pbf.buffer.byteLength) { + // Compatibility with node Buffer (https://github.com/mapbox/pbf/issues/35) + pbf = new Uint8Array(pbf); + } + callback(null, { tile: geojsonWrapper, rawTileData: pbf.buffer }); + // tile.parse(geojsonWrapper, this.layerFamilies, this.actor, rawTileData, callback); + } else { + return callback(null, null); // nothing in the given tile + } + }, + + /** + * Fetches (if appropriate), parses, and index geojson data into tiles. This + * preparatory method must be called before {@link GeoJSONWorkerSource#loadTile} + * can correctly serve up tiles. + * + * Defers to {@link GeoJSONWorkerSource#loadGeoJSON} for the fetching/parsing, + * expecting `callback(error, data)` to be called with either an error or a + * parsed GeoJSON object. + * @param {object} params + * @param {string} params.source The id of the source. + * @param {Function} callback + */ + loadData: function (params, callback) { + var handleData = function(err, data) { + if (err) return callback(err); + if (typeof data != 'object') { + return callback(new Error("Input data is not a valid GeoJSON object.")); + } + rewind(data, true); + this._indexData(data, params, function (err, indexed) { + if (err) { return callback(err); } + this._geoJSONIndexes[params.source] = indexed; + callback(null); + }.bind(this)); + }.bind(this); + + this.loadGeoJSON(params, handleData); + }, + + /** + * Fetch and parse GeoJSON according to the given params. Calls `callback` + * with `(err, data)`, where `data` is a parsed GeoJSON object. + * + * GeoJSON is loaded and parsed from `params.url` if it exists, or else + * expected as a literal (string or object) `params.data`. + * + * @param {object} params + * @param {string} [params.url] A URL to the remote GeoJSON data. + * @param {object} [params.data] Literal GeoJSON data. Must be provided if `params.url` is not. + */ + loadGeoJSON: function (params, callback) { + // Because of same origin issues, urls must either include an explicit + // origin or absolute path. + // ie: /foo/bar.json or http://example.com/bar.json + // but not ../foo/bar.json + if (params.url) { + ajax.getJSON(params.url, callback); + } else if (typeof params.data === 'string') { + try { + return callback(null, JSON.parse(params.data)); + } catch (e) { + return callback(new Error("Input data is not a valid GeoJSON object.")); + } + } else { + return callback(new Error("Input data is not a valid GeoJSON object.")); + } + }, + + /** + * Index the data using either geojson-vt or supercluster + * @param {GeoJSON} data + * @param {object} params forwarded from loadTile. + * @param {callback} (err, indexedData) + * @private + */ + _indexData: function (data, params, callback) { + try { + if (params.cluster) { + callback(null, supercluster(params.superclusterOptions).load(data.features)); + } else { + callback(null, geojsonvt(data, params.geojsonVtOptions)); + } + } catch (err) { + return callback(err); + } + } +}); diff --git a/js/source/image_source.js b/js/source/image_source.js index 0c68e90c31c..5ff674f900f 100644 --- a/js/source/image_source.js +++ b/js/source/image_source.js @@ -1,7 +1,6 @@ 'use strict'; var util = require('../util/util'); -var Tile = require('./tile'); var TileCoord = require('./tile_coord'); var LngLat = require('../geo/lng_lat'); var Point = require('point-geometry'); @@ -16,34 +15,41 @@ module.exports = ImageSource; /** * A data source containing an image. + * (See the [Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/#sources-image) for detailed documentation of options.) * - * @class ImageSource - * @param {Object} options - * @param {string} options.url The URL of an image file. - * @param {Array>} options.coordinates Four geographical coordinates, - * represented as arrays of longitude and latitude numbers, which define the corners of the image. - * The coordinates start at the top left corner of the image and proceed in clockwise order. - * They do not have to represent a rectangle. + * @interface ImageSource * @example - * var sourceObj = new mapboxgl.ImageSource({ + * // add to map + * map.addSource('some id', { + * type: 'image', * url: 'https://www.mapbox.com/images/foo.png', * coordinates: [ - * [-76.54335737228394, 39.18579907229748], - * [-76.52803659439087, 39.1838364847587], - * [-76.5295386314392, 39.17683392507606], - * [-76.54520273208618, 39.17876344106642] + * [-76.54, 39.18], + * [-76.52, 39.18], + * [-76.52, 39.17], + * [-76.54, 39.17] * ] * }); - * map.addSource('some id', sourceObj); // add + * + * // update + * var mySource = map.getSource('some id'); + * mySource.setCoordinates([ + * [-76.54335737228394, 39.18579907229748], + * [-76.52803659439087, 39.1838364847587], + * [-76.5295386314392, 39.17683392507606], + * [-76.54520273208618, 39.17876344106642] + * ]); + * * map.removeSource('some id'); // remove */ -function ImageSource(options) { +function ImageSource(id, options, dispatcher) { + this.id = id; + this.dispatcher = dispatcher; this.url = options.url; this.coordinates = options.coordinates; ajax.getImage(options.url, function(err, image) { - // @TODO handle errors via event. - if (err) return; + if (err) return this.fire('error', {error: err}); this.image = image; @@ -52,6 +58,7 @@ function ImageSource(options) { }.bind(this)); this._loaded = true; + this.fire('load'); if (this.map) { this.setCoordinates(options.coordinates); @@ -60,6 +67,9 @@ function ImageSource(options) { } ImageSource.prototype = util.inherit(Evented, /** @lends ImageSource.prototype */ { + minzoom: 0, + maxzoom: 22, + tileSize: 512, onAdd: function(map) { this.map = map; if (this.image) { @@ -79,7 +89,7 @@ ImageSource.prototype = util.inherit(Evented, /** @lends ImageSource.prototype * setCoordinates: function(coordinates) { this.coordinates = coordinates; - // Calculate which mercator tile is suitable for rendering the image in + // Calculate which mercator tile is suitable for rendering the video in // and create a buffer with the corner coordinates. These coordinates // may be outside the tile, because raster tiles aren't clipped when rendering. @@ -92,50 +102,44 @@ ImageSource.prototype = util.inherit(Evented, /** @lends ImageSource.prototype * centerCoord.column = Math.round(centerCoord.column); centerCoord.row = Math.round(centerCoord.row); - var tileCoords = cornerZ0Coords.map(function(coord) { + this.minzoom = this.maxzoom = centerCoord.zoom; + this._coord = new TileCoord(centerCoord.zoom, centerCoord.column, centerCoord.row); + this._tileCoords = cornerZ0Coords.map(function(coord) { var zoomedCoord = coord.zoomTo(centerCoord.zoom); return new Point( Math.round((zoomedCoord.column - centerCoord.column) * EXTENT), Math.round((zoomedCoord.row - centerCoord.row) * EXTENT)); }); + this.fire('change'); + return this; + }, + + _setTile: function (tile) { + this._prepared = false; + this.tile = tile; var maxInt16 = 32767; var array = new RasterBoundsArray(); - array.emplaceBack(tileCoords[0].x, tileCoords[0].y, 0, 0); - array.emplaceBack(tileCoords[1].x, tileCoords[1].y, maxInt16, 0); - array.emplaceBack(tileCoords[3].x, tileCoords[3].y, 0, maxInt16); - array.emplaceBack(tileCoords[2].x, tileCoords[2].y, maxInt16, maxInt16); + array.emplaceBack(this._tileCoords[0].x, this._tileCoords[0].y, 0, 0); + array.emplaceBack(this._tileCoords[1].x, this._tileCoords[1].y, maxInt16, 0); + array.emplaceBack(this._tileCoords[3].x, this._tileCoords[3].y, 0, maxInt16); + array.emplaceBack(this._tileCoords[2].x, this._tileCoords[2].y, maxInt16, maxInt16); - this.tile = new Tile(new TileCoord(centerCoord.zoom, centerCoord.column, centerCoord.row)); this.tile.buckets = {}; this.tile.boundsBuffer = new Buffer(array.serialize(), RasterBoundsArray.serialize(), Buffer.BufferType.VERTEX); this.tile.boundsVAO = new VertexArrayObject(); - - this.fire('change'); - - return this; - }, - - loaded: function() { - return this.image && this.image.complete; - }, - - update: function() { - // noop - }, - - reload: function() { - // noop + this.tile.state = 'loaded'; }, prepare: function() { - if (!this._loaded || !this.loaded()) return; + if (!this._loaded || !this.image || !this.image.complete) return; + if (!this.tile) return; var painter = this.map.painter; var gl = painter.gl; - if (!this.tile.texture) { + if (!this._prepared) { this.tile.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.tile.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); @@ -149,13 +153,18 @@ ImageSource.prototype = util.inherit(Evented, /** @lends ImageSource.prototype * } }, - getVisibleCoordinates: function() { - if (this.tile) return [this.tile.coord]; - else return []; - }, - - getTile: function() { - return this.tile; + loadTile: function(tile, callback) { + // We have a single tile -- whoose coordinates are this._coord -- that + // covers the image we want to render. If that's the one being + // requested, set it up with the image; otherwise, mark the tile as + // `errored` to indicate that we have no data for it. + if (this._coord && this._coord.toString() === tile.coord.toString()) { + this._setTile(tile); + callback(null); + } else { + tile.state = 'errored'; + callback(null); + } }, serialize: function() { diff --git a/js/source/load_tilejson.js b/js/source/load_tilejson.js new file mode 100644 index 00000000000..0da31a4efe8 --- /dev/null +++ b/js/source/load_tilejson.js @@ -0,0 +1,29 @@ +'use strict'; +var util = require('../util/util'); +var ajax = require('../util/ajax'); +var browser = require('../util/browser'); +var normalizeURL = require('../util/mapbox').normalizeSourceURL; + +module.exports = function(options, callback) { + var loaded = function(err, tileJSON) { + if (err) { + return callback(err); + } + + var result = util.pick(tileJSON, ['tiles', 'minzoom', 'maxzoom', 'attribution']); + + if (tileJSON.vector_layers) { + result.vectorLayers = tileJSON.vector_layers; + result.vectorLayerIds = result.vectorLayers.map(function(layer) { return layer.id; }); + } + + callback(null, result); + }; + + if (options.url) { + ajax.getJSON(normalizeURL(options.url), loaded); + } else { + browser.frame(loaded.bind(null, null, options)); + } +}; + diff --git a/js/source/query_features.js b/js/source/query_features.js new file mode 100644 index 00000000000..436e0ea4d6f --- /dev/null +++ b/js/source/query_features.js @@ -0,0 +1,69 @@ +'use strict'; +var TileCoord = require('./tile_coord'); + +exports.rendered = function(sourceCache, styleLayers, queryGeometry, params, zoom, bearing) { + var tilesIn = sourceCache.tilesIn(queryGeometry); + + tilesIn.sort(sortTilesIn); + + var renderedFeatureLayers = []; + for (var r = 0; r < tilesIn.length; r++) { + var tileIn = tilesIn[r]; + if (!tileIn.tile.featureIndex) continue; + + renderedFeatureLayers.push(tileIn.tile.featureIndex.query({ + queryGeometry: tileIn.queryGeometry, + scale: tileIn.scale, + tileSize: tileIn.tile.tileSize, + bearing: bearing, + params: params + }, styleLayers)); + } + return mergeRenderedFeatureLayers(renderedFeatureLayers); +}; + +exports.source = function(sourceCache, params) { + var tiles = sourceCache.renderedIDs().map(function(id) { + return sourceCache.getTileByID(id); + }); + + var result = []; + + var dataTiles = {}; + for (var i = 0; i < tiles.length; i++) { + var tile = tiles[i]; + var dataID = new TileCoord(Math.min(tile.sourceMaxZoom, tile.coord.z), tile.coord.x, tile.coord.y, 0).id; + if (!dataTiles[dataID]) { + dataTiles[dataID] = true; + tile.querySourceFeatures(result, params); + } + } + + return result; +}; + +function sortTilesIn(a, b) { + var coordA = a.coord; + var coordB = b.coord; + return (coordA.z - coordB.z) || (coordA.y - coordB.y) || (coordA.w - coordB.w) || (coordA.x - coordB.x); +} + +function mergeRenderedFeatureLayers(tiles) { + var result = tiles[0] || {}; + for (var i = 1; i < tiles.length; i++) { + var tile = tiles[i]; + for (var layerID in tile) { + var tileFeatures = tile[layerID]; + var resultFeatures = result[layerID]; + if (resultFeatures === undefined) { + resultFeatures = result[layerID] = tileFeatures; + } else { + for (var f = 0; f < tileFeatures.length; f++) { + resultFeatures.push(tileFeatures[f]); + } + } + } + } + return result; +} + diff --git a/js/source/raster_tile_source.js b/js/source/raster_tile_source.js index a41f28838b3..567b88d0aee 100644 --- a/js/source/raster_tile_source.js +++ b/js/source/raster_tile_source.js @@ -3,15 +3,22 @@ var util = require('../util/util'); var ajax = require('../util/ajax'); var Evented = require('../util/evented'); -var Source = require('./source'); +var loadTileJSON = require('./load_tilejson'); var normalizeURL = require('../util/mapbox').normalizeTileURL; module.exports = RasterTileSource; -function RasterTileSource(options) { +function RasterTileSource(id, options, dispatcher) { + this.id = id; + this.dispatcher = dispatcher; util.extend(this, util.pick(options, ['url', 'scheme', 'tileSize'])); - - Source._loadTileJSON.call(this, options); + loadTileJSON(options, function (err, tileJSON) { + if (err) { + return this.fire('error', err); + } + util.extend(this, tileJSON); + this.fire('load'); + }.bind(this)); } RasterTileSource.prototype = util.inherit(Evented, { @@ -22,24 +29,10 @@ RasterTileSource.prototype = util.inherit(Evented, { tileSize: 512, _loaded: false, - onAdd: function(map) { + onAdd: function (map) { this.map = map; }, - loaded: function() { - return this._pyramid && this._pyramid.loaded(); - }, - - update: function(transform) { - if (this._pyramid) { - this._pyramid.update(this.used, transform, this.map.style.rasterFadeDuration); - } - }, - - reload: function() { - // noop - }, - serialize: function() { return { type: 'raster', @@ -48,10 +41,7 @@ RasterTileSource.prototype = util.inherit(Evented, { }; }, - getVisibleCoordinates: Source._getVisibleCoordinates, - getTile: Source._getTile, - - _loadTile: function(tile) { + loadTile: function(tile, callback) { var url = normalizeURL(tile.coord.url(this.tiles, null, this.scheme), this.url, this.tileSize); tile.request = ajax.getImage(url, done.bind(this)); @@ -63,9 +53,7 @@ RasterTileSource.prototype = util.inherit(Evented, { return; if (err) { - tile.state = 'errored'; - this.fire('tile.error', {tile: tile, error: err}); - return; + return callback(err); } var gl = this.map.painter.gl; @@ -86,34 +74,22 @@ RasterTileSource.prototype = util.inherit(Evented, { } gl.generateMipmap(gl.TEXTURE_2D); - tile.timeAdded = new Date().getTime(); - this.map.animationLoop.set(this.style.rasterFadeDuration); + this.map.animationLoop.set(this.map.style.rasterFadeDuration); - tile.source = this; tile.state = 'loaded'; - this.fire('tile.load', {tile: tile}); + callback(null); } }, - _abortTile: function(tile) { - tile.aborted = true; - + abortTile: function(tile) { if (tile.request) { tile.request.abort(); delete tile.request; } }, - _addTile: function(tile) { - this.fire('tile.add', {tile: tile}); - }, - - _removeTile: function(tile) { - this.fire('tile.remove', {tile: tile}); - }, - - _unloadTile: function(tile) { + unloadTile: function(tile) { if (tile.texture) this.map.painter.saveTexture(tile.texture); } }); diff --git a/js/source/source.js b/js/source/source.js index 667212182c7..7d98b4d7fad 100644 --- a/js/source/source.js +++ b/js/source/source.js @@ -1,193 +1,169 @@ 'use strict'; var util = require('../util/util'); -var ajax = require('../util/ajax'); -var browser = require('../util/browser'); -var TilePyramid = require('./tile_pyramid'); -var normalizeURL = require('../util/mapbox').normalizeSourceURL; -var TileCoord = require('./tile_coord'); - -exports._loadTileJSON = function(options) { - var loaded = function(err, tileJSON) { - if (err) { - this.fire('error', {error: err}); - return; - } - - util.extend(this, util.pick(tileJSON, - ['tiles', 'minzoom', 'maxzoom', 'attribution'])); - - if (tileJSON.vector_layers) { - this.vectorLayers = tileJSON.vector_layers; - this.vectorLayerIds = this.vectorLayers.map(function(layer) { return layer.id; }); - } - - this._pyramid = new TilePyramid({ - tileSize: this.tileSize, - minzoom: this.minzoom, - maxzoom: this.maxzoom, - roundZoom: this.roundZoom, - reparseOverscaled: this.reparseOverscaled, - load: this._loadTile.bind(this), - abort: this._abortTile.bind(this), - unload: this._unloadTile.bind(this), - add: this._addTile.bind(this), - remove: this._removeTile.bind(this), - redoPlacement: this._redoTilePlacement ? this._redoTilePlacement.bind(this) : undefined - }); - - this.fire('load'); - }.bind(this); - - if (options.url) { - ajax.getJSON(normalizeURL(options.url), loaded); - } else { - browser.frame(loaded.bind(this, null, options)); - } + +var sourceTypes = { + 'vector': require('../source/vector_tile_source'), + 'raster': require('../source/raster_tile_source'), + 'geojson': require('../source/geojson_source'), + 'video': require('../source/video_source'), + 'image': require('../source/image_source') }; -exports.redoPlacement = function() { - if (!this._pyramid) { - return; - } +/* + * Creates a tiled data source instance given an options object. + * + * @param {string} id + * @param {Object} source A source definition object compliant with [`mapbox-gl-style-spec`](https://www.mapbox.com/mapbox-gl-style-spec/#sources) or, for a third-party source type, with that type's requirements. + * @param {string} options.type A source type like `raster`, `vector`, `video`, etc. + * @param {Dispatcher} dispatcher + * @returns {Source} + */ +exports.create = function(id, source, dispatcher) { + source = new sourceTypes[source.type](id, source, dispatcher); - var ids = this._pyramid.getIds(); - for (var i = 0; i < ids.length; i++) { - var tile = this._pyramid.getTile(ids[i]); - this._redoTilePlacement(tile); + if (source.id !== id) { + throw new Error('Expected Source id to be ' + id + ' instead of ' + source.id); } + + util.bindAll(['load', 'abort', 'unload', 'serialize', 'prepare'], source); + return source; }; -exports._getTile = function(coord) { - return this._pyramid.getTile(coord.id); +exports.getType = function (name) { + return sourceTypes[name]; }; -exports._getVisibleCoordinates = function() { - if (!this._pyramid) return []; - else return this._pyramid.getRenderableIds().map(TileCoord.fromID); +exports.setType = function (name, type) { + sourceTypes[name] = type; }; -function sortTilesIn(a, b) { - var coordA = a.coord; - var coordB = b.coord; - return (coordA.z - coordB.z) || (coordA.y - coordB.y) || (coordA.w - coordB.w) || (coordA.x - coordB.x); -} - -function mergeRenderedFeatureLayers(tiles) { - var result = tiles[0] || {}; - for (var i = 1; i < tiles.length; i++) { - var tile = tiles[i]; - for (var layerID in tile) { - var tileFeatures = tile[layerID]; - var resultFeatures = result[layerID]; - if (resultFeatures === undefined) { - resultFeatures = result[layerID] = tileFeatures; - } else { - for (var f = 0; f < tileFeatures.length; f++) { - resultFeatures.push(tileFeatures[f]); - } - } - } - } - return result; -} +/** + * The `Source` interface must be implemented by each source type, including "core" types (`vector`, `raster`, `video`, etc.) and all custom, third-party types. + * + * @class Source + * @private + * + * @param {string} id The id for the source. Must not be used by any existing source. + * @param {Object} options Source options, specific to the source type (except for `options.type`, which is always required). + * @param {string} options.type The source type, matching the value of `name` used in {@link Style#addSourceType}. + * @param {Dispatcher} dispatcher A {@link Dispatcher} instance, which can be used to send messages to the workers. + * + * @fires load to indicate source data has been loaded, so that it's okay to call `loadTile` + * @fires change to indicate source data has changed, so that any current caches should be flushed + * @property {string} id The id for the source. Must match the id passed to the constructor. + * @property {number} minzoom + * @property {number} maxzoom + * @property {boolean} isTileClipped `false` if tiles can be drawn outside their boundaries, `true` if they cannot. + * @property {boolean} reparseOverscaled `true` if tiles should be sent back to the worker for each overzoomed zoom level, `false` if not. + * @property {boolean} roundZoom `true` if zoom levels are rounded to the nearest integer in the source data, `false` if they are floor-ed to the nearest integer. + */ -exports._queryRenderedVectorFeatures = function(queryGeometry, params, zoom, bearing) { - if (!this._pyramid || !this.map) - return []; +/** + * An optional URL to a script which, when run by a Worker, registers a {@link WorkerSource} implementation for this Source type by calling `self.registerWorkerSource(workerSource: WorkerSource)`. + * + * @member {URL|undefined} workerSourceURL + * @memberof Source + * @static + */ - var tilesIn = this._pyramid.tilesIn(queryGeometry); +/** + * @method + * @name loadTile + * @param {Tile} tile + * @param {Funtion} callback Called when tile has been loaded + * @memberof Source + * @instance + */ + +/** + * @method + * @name abortTile + * @param {Tile} tile + * @memberof Source + * @instance + */ - tilesIn.sort(sortTilesIn); +/** + * @method + * @name unloadTile + * @param {Tile} tile + * @memberof Source + * @instance + */ - var styleLayers = this.map.style._layers; +/** + * @method + * @name serialize + * @returns {Object} A plain (stringifiable) JS object representing the current state of the source. Creating a source using the returned object as the `options` should result in a Source that is equivalent to this one. + * @memberof Source + * @instance + */ - var renderedFeatureLayers = []; - for (var r = 0; r < tilesIn.length; r++) { - var tileIn = tilesIn[r]; - if (!tileIn.tile.featureIndex) continue; +/** + * @method + * @name prepare + * @memberof Source + * @instance + */ - renderedFeatureLayers.push(tileIn.tile.featureIndex.query({ - queryGeometry: tileIn.queryGeometry, - scale: tileIn.scale, - tileSize: tileIn.tile.tileSize, - bearing: bearing, - params: params - }, styleLayers)); - } - return mergeRenderedFeatureLayers(renderedFeatureLayers); -}; -exports._querySourceFeatures = function(params) { - if (!this._pyramid) { - return []; - } - var pyramid = this._pyramid; - var tiles = pyramid.getRenderableIds().map(function(id) { - return pyramid.getTile(id); - }); - - var result = []; - - var dataTiles = {}; - for (var i = 0; i < tiles.length; i++) { - var tile = tiles[i]; - var dataID = new TileCoord(Math.min(tile.sourceMaxZoom, tile.coord.z), tile.coord.x, tile.coord.y, 0).id; - if (!dataTiles[dataID]) { - dataTiles[dataID] = true; - tile.querySourceFeatures(result, params); - } - } +/** + * May be implemented by custom source types to provide code that can be run on + * the WebWorkers. In addition to providing a custom + * {@link WorkerSource#loadTile}, any other methods attached to a `WorkerSource` + * implementation may also be targeted by the {@link Source} via + * `dispatcher.send('source-type.methodname', params, callback)`. + * + * @see {@link Map#addSourceType} + * @private + * + * @class WorkerSource + * @param {Actor} actor + * @param {object} styleLayers An accessor provided by the Worker to get the current style layers and layer families. + * @param {Function} styleLayers.getLayers + * @param {Function} styleLayers.getLayerFamilies + */ - return result; -}; +/** + * Loads a tile from the given params and parse it into buckets ready to send + * back to the main thread for rendering. Should call the callback with: + * `{ buckets, featureIndex, collisionTile, symbolInstancesArray, symbolQuadsArray, rawTileData}`. + * + * @method + * @name loadTile + * @param {object} params Parameters sent by the main-thread Source identifying the tile to load. + * @param {Function} callback + * @memberof WorkerSource + * @instance + */ -/* - * Create a tiled data source instance given an options object +/** + * Re-parses a tile that has already been loaded. Yields the same data as + * {@link WorkerSource#loadTile}. * - * @param {Object} options - * @param {string} options.type Either `raster` or `vector`. - * @param {string} options.url A tile source URL. This should either be `mapbox://{mapid}` or a full `http[s]` url that points to a TileJSON endpoint. - * @param {Array} options.tiles An array of tile sources. If `url` is not specified, `tiles` can be used instead to specify tile sources, as in the TileJSON spec. Other TileJSON keys such as `minzoom` and `maxzoom` can be specified in a source object if `tiles` is used. - * @param {string} options.id An optional `id` to assign to the source - * @param {number} [options.tileSize=512] Optional tile size (width and height in pixels, assuming tiles are square). This option is only configurable for raster sources - * @example - * var sourceObj = new mapboxgl.Source.create({ - * type: 'vector', - * url: 'mapbox://mapbox.mapbox-streets-v6' - * }); - * map.addSource('some id', sourceObj); // add - * map.removeSource('some id'); // remove + * @method + * @name reloadTile + * @param {object} params + * @param {Function} callback + * @memberof WorkerSource + * @instance */ -exports.create = function(source) { - // This is not at file scope in order to avoid a circular require. - var sources = { - vector: require('./vector_tile_source'), - raster: require('./raster_tile_source'), - geojson: require('./geojson_source'), - video: require('./video_source'), - image: require('./image_source') - }; - - return exports.is(source) ? source : new sources[source.type](source); -}; -exports.is = function(source) { - // This is not at file scope in order to avoid a circular require. - var sources = { - vector: require('./vector_tile_source'), - raster: require('./raster_tile_source'), - geojson: require('./geojson_source'), - video: require('./video_source'), - image: require('./image_source') - }; - - for (var type in sources) { - if (source instanceof sources[type]) { - return true; - } - } +/** + * Aborts loading a tile that is in progress. + * @method + * @name abortTile + * @param {object} params + * @memberof WorkerSource + * @instance + */ - return false; -}; +/** + * Removes this tile from any local caches. + * @method + * @name removeTile + * @memberof WorkerSource + * @instance + */ diff --git a/js/source/tile_pyramid.js b/js/source/source_cache.js similarity index 75% rename from js/source/tile_pyramid.js rename to js/source/source_cache.js index 454967def90..c72a10dd4ce 100644 --- a/js/source/tile_pyramid.js +++ b/js/source/source_cache.js @@ -1,14 +1,15 @@ 'use strict'; +var Source = require('./source'); var Tile = require('./tile'); +var Evented = require('../util/evented'); var TileCoord = require('./tile_coord'); -var Point = require('point-geometry'); var Cache = require('../util/lru_cache'); var Coordinate = require('../geo/coordinate'); var util = require('../util/util'); var EXTENT = require('../data/bucket').EXTENT; -module.exports = TilePyramid; +module.exports = SourceCache; /** * A tile pyramid is a specialized cache and datastructure @@ -16,36 +17,59 @@ module.exports = TilePyramid; * data. * * @param {Object} options - * @param {number} options.tileSize - * @param {number} options.minzoom - * @param {number} options.maxzoom * @private */ -function TilePyramid(options) { - this.tileSize = options.tileSize; - this.minzoom = options.minzoom; - this.maxzoom = options.maxzoom; - this.roundZoom = options.roundZoom; - this.reparseOverscaled = options.reparseOverscaled; - - this._load = options.load; - this._abort = options.abort; - this._unload = options.unload; - this._add = options.add; - this._remove = options.remove; - this._redoPlacement = options.redoPlacement; +function SourceCache(id, options, dispatcher) { + this.id = id; + this.dispatcher = dispatcher; + + var source = this._source = Source.create(id, options, dispatcher) + .on('load', function () { + if (this.map && this._source.onAdd) { this._source.onAdd(this.map); } + + this._sourceLoaded = true; + + this.tileSize = source.tileSize; + this.minzoom = source.minzoom; + this.maxzoom = source.maxzoom; + this.roundZoom = source.roundZoom; + this.reparseOverscaled = source.reparseOverscaled; + this.isTileClipped = source.isTileClipped; + + this.vectorLayerIds = source.vectorLayerIds; + + this.fire('load'); + }.bind(this)) + .on('error', function (e) { + this._sourceErrored = true; + this.fire('error', e); + }.bind(this)) + .on('change', function () { + this.reload(); + if (this.transform) { + this.update(this.transform, this.map && this.map.style.rasterFadeDuration); + } + this.fire('change'); + }.bind(this)); this._tiles = {}; - this._cache = new Cache(0, function(tile) { return this._unload(tile); }.bind(this)); + this._cache = new Cache(0, this.unloadTile.bind(this)); this._isIdRenderable = this._isIdRenderable.bind(this); } -TilePyramid.maxOverzooming = 10; -TilePyramid.maxUnderzooming = 3; +SourceCache.maxOverzooming = 10; +SourceCache.maxUnderzooming = 3; + +SourceCache.prototype = util.inherit(Evented, { + onAdd: function (map) { + this.map = map; + if (this._source && this._source.onAdd) { + this._source.onAdd(map); + } + }, -TilePyramid.prototype = { /** * Return true if no tile data is pending, tiles will not change unless * an additional API call is received. @@ -53,6 +77,8 @@ TilePyramid.prototype = { * @private */ loaded: function() { + if (this._sourceErrored) { return true; } + if (!this._sourceLoaded) { return false; } for (var t in this._tiles) { var tile = this._tiles[t]; if (tile.state !== 'loaded' && tile.state !== 'errored') @@ -61,6 +87,37 @@ TilePyramid.prototype = { return true; }, + /** + * @returns {Source} The underlying source object + * @private + */ + getSource: function () { + return this._source; + }, + + loadTile: function (tile, callback) { + return this._source.loadTile(tile, callback); + }, + + unloadTile: function (tile) { + if (this._source.unloadTile) + return this._source.unloadTile(tile); + }, + + abortTile: function (tile) { + if (this._source.abortTile) + return this._source.abortTile(tile); + }, + + serialize: function () { + return this._source.serialize(); + }, + + prepare: function () { + if (this._sourceLoaded && this._source.prepare) + return this._source.prepare(); + }, + /** * Return all tile ids ordered with z-order, and cast to numbers * @returns {Array} ids @@ -91,66 +148,52 @@ TilePyramid.prototype = { tile.state = 'reloading'; } - this._load(tile); + this.loadTile(this._tiles[i], this._tileLoaded.bind(this, this._tiles[i])); } }, - /** - * Get a specific tile by id - * @param {string|number} id tile id - * @returns {Object} tile - * @private - */ - getTile: function(id) { - return this._tiles[id]; + _tileLoaded: function (tile, err) { + if (err) { + tile.state = 'errored'; + this.fire('tile.error', {tile: tile, error: err}); + this._source.fire('tile.error', {tile: tile, error: err}); + return; + } + + tile.source = this; + tile.timeAdded = new Date().getTime(); + this.fire('tile.load', {tile: tile}); + this._source.fire('tile.load', {tile: tile}); }, /** - * get the zoom level adjusted for the difference in map and source tilesizes - * @param {Object} transform - * @returns {number} zoom level + * Get a specific tile by TileCoordinate + * @param {TileCoordinate} coord + * @returns {Object} tile * @private */ - getZoom: function(transform) { - return transform.zoom + Math.log(transform.tileSize / this.tileSize) / Math.LN2; + getTile: function(coord) { + return this.getTileByID(coord.id); }, /** - * Return a zoom level that will cover all tiles in a given transform - * @param {Object} transform - * @returns {number} zoom level + * Get a specific tile by id + * @param {number|string} id + * @returns {Object} tile * @private */ - coveringZoomLevel: function(transform) { - return (this.roundZoom ? Math.round : Math.floor)(this.getZoom(transform)); + getTileByID: function(id) { + return this._tiles[id]; }, /** - * Given a transform, return all coordinates that could cover that - * transform for a covering zoom level. + * get the zoom level adjusted for the difference in map and source tilesizes * @param {Object} transform - * @returns {Array} tiles + * @returns {number} zoom level * @private */ - coveringTiles: function(transform) { - var z = this.coveringZoomLevel(transform); - var actualZ = z; - - if (z < this.minzoom) return []; - if (z > this.maxzoom) z = this.maxzoom; - - var tr = transform, - tileCenter = tr.locationCoordinate(tr.center)._zoomTo(z), - centerPoint = new Point(tileCenter.column - 0.5, tileCenter.row - 0.5); - - return TileCoord.cover(z, [ - tr.pointCoordinate(new Point(0, 0))._zoomTo(z), - tr.pointCoordinate(new Point(tr.width, 0))._zoomTo(z), - tr.pointCoordinate(new Point(tr.width, tr.height))._zoomTo(z), - tr.pointCoordinate(new Point(0, tr.height))._zoomTo(z) - ], this.reparseOverscaled ? actualZ : z).sort(function(a, b) { - return centerPoint.dist(a) - centerPoint.dist(b); - }); + getZoom: function(transform) { + return transform.zoom + transform.scaleZoom(transform.tileSize / this.tileSize); }, /** @@ -244,7 +287,8 @@ TilePyramid.prototype = { * are inside the viewport. * @private */ - update: function(used, transform, fadeDuration) { + update: function(transform, fadeDuration) { + if (!this._sourceLoaded) { return; } var i; var coord; var tile; @@ -253,8 +297,8 @@ TilePyramid.prototype = { // Determine the overzooming/underzooming amounts. var zoom = (this.roundZoom ? Math.round : Math.floor)(this.getZoom(transform)); - var minCoveringZoom = Math.max(zoom - TilePyramid.maxOverzooming, this.minzoom); - var maxCoveringZoom = Math.max(zoom + TilePyramid.maxUnderzooming, this.minzoom); + var minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this.minzoom); + var maxCoveringZoom = Math.max(zoom + SourceCache.maxUnderzooming, this.minzoom); // Retain is a list of tiles that we shouldn't delete, even if they are not // the most ideal tile for the current viewport. This may include tiles like @@ -266,7 +310,7 @@ TilePyramid.prototype = { // better, retained tiles. They are not drawn separately. this._coveredTiles = {}; - var required = used ? this.coveringTiles(transform) : []; + var required = this.used ? transform.coveringTiles(this._source) : []; for (i = 0; i < required.length; i++) { coord = required[i]; tile = this.addTile(coord); @@ -344,12 +388,13 @@ TilePyramid.prototype = { var zoom = coord.z; var overscaling = zoom > this.maxzoom ? Math.pow(2, zoom - this.maxzoom) : 1; tile = new Tile(wrapped, this.tileSize * overscaling, this.maxzoom); - this._load(tile); + this.loadTile(tile, this._tileLoaded.bind(this, tile)); } tile.uses++; this._tiles[coord.id] = tile; - this._add(tile, coord); + this.fire('tile.add', {tile: tile}); + this._source.fire('tile.add', {tile: tile}); return tile; }, @@ -367,7 +412,8 @@ TilePyramid.prototype = { tile.uses--; delete this._tiles[id]; - this._remove(tile); + this.fire('tile.remove', {tile: tile}); + this._source.fire('tile.remove', {tile: tile}); if (tile.uses > 0) return; @@ -375,8 +421,9 @@ TilePyramid.prototype = { if (tile.isRenderable()) { this._cache.add(tile.coord.wrapped().id, tile); } else { - this._abort(tile); - this._unload(tile); + tile.aborted = true; + this.abortTile(tile); + this.unloadTile(tile); } }, @@ -452,8 +499,20 @@ TilePyramid.prototype = { results.push(tileResults[t]); } return results; + }, + + redoPlacement: function () { + var ids = this.getIds(); + for (var i = 0; i < ids.length; i++) { + var tile = this.getTileByID(ids[i]); + tile.redoPlacement(this); + } + }, + + getVisibleCoordinates: function () { + return this.getRenderableIds().map(TileCoord.fromID); } -}; +}); /** * Convert a coordinate to a point in a tile's coordinate space. diff --git a/js/source/vector_tile_source.js b/js/source/vector_tile_source.js index 5cc5fd6462f..05bd1206f5e 100644 --- a/js/source/vector_tile_source.js +++ b/js/source/vector_tile_source.js @@ -1,13 +1,15 @@ 'use strict'; -var util = require('../util/util'); var Evented = require('../util/evented'); -var Source = require('./source'); +var util = require('../util/util'); +var loadTileJSON = require('./load_tilejson'); var normalizeURL = require('../util/mapbox').normalizeTileURL; module.exports = VectorTileSource; -function VectorTileSource(options) { +function VectorTileSource(id, options, dispatcher) { + this.id = id; + this.dispatcher = dispatcher; util.extend(this, util.pick(options, ['url', 'scheme', 'tileSize'])); this._options = util.extend({ type: 'vector' }, options); @@ -15,7 +17,14 @@ function VectorTileSource(options) { throw new Error('vector tile sources must have a tileSize of 512'); } - Source._loadTileJSON.call(this, options); + loadTileJSON(options, function (err, tileJSON) { + if (err) { + this.fire('error', err); + return; + } + util.extend(this, tileJSON); + this.fire('load'); + }.bind(this)); } VectorTileSource.prototype = util.inherit(Evented, { @@ -24,40 +33,17 @@ VectorTileSource.prototype = util.inherit(Evented, { scheme: 'xyz', tileSize: 512, reparseOverscaled: true, - _loaded: false, isTileClipped: true, onAdd: function(map) { this.map = map; }, - loaded: function() { - return this._pyramid && this._pyramid.loaded(); - }, - - update: function(transform) { - if (this._pyramid) { - this._pyramid.update(this.used, transform); - } - }, - - reload: function() { - if (this._pyramid) { - this._pyramid.reload(); - } - }, - serialize: function() { return util.extend({}, this._options); }, - getVisibleCoordinates: Source._getVisibleCoordinates, - getTile: Source._getTile, - - queryRenderedFeatures: Source._queryRenderedVectorFeatures, - querySourceFeatures: Source._querySourceFeatures, - - _loadTile: function(tile) { + loadTile: function(tile, callback) { var overscaling = tile.coord.z > this.maxzoom ? Math.pow(2, tile.coord.z - this.maxzoom) : 1; var params = { url: normalizeURL(tile.coord.url(this.tiles, this.maxzoom, this.scheme), this.url), @@ -74,54 +60,36 @@ VectorTileSource.prototype = util.inherit(Evented, { if (tile.workerID) { params.rawTileData = tile.rawTileData; - this.dispatcher.send('reload tile', params, this._tileLoaded.bind(this, tile), tile.workerID); + this.dispatcher.send('reload tile', params, done.bind(this), tile.workerID); } else { - tile.workerID = this.dispatcher.send('load tile', params, this._tileLoaded.bind(this, tile)); + tile.workerID = this.dispatcher.send('load tile', params, done.bind(this)); } - }, - _tileLoaded: function(tile, err, data) { - if (tile.aborted) - return; + function done(err, data) { + if (tile.aborted) + return; - if (err) { - tile.state = 'errored'; - this.fire('tile.error', {tile: tile, error: err}); - return; - } + if (err) { + return callback(err); + } - tile.loadVectorData(data, this.map.style); + tile.loadVectorData(data, this.map.style); - if (tile.redoWhenDone) { - tile.redoWhenDone = false; - tile.redoPlacement(this); - } + if (tile.redoWhenDone) { + tile.redoWhenDone = false; + tile.redoPlacement(this); + } - this.fire('tile.load', {tile: tile}); - this.fire('tile.stats', data.bucketStats); + callback(null); + } }, - _abortTile: function(tile) { - tile.aborted = true; + abortTile: function(tile) { this.dispatcher.send('abort tile', { uid: tile.uid, source: this.id }, null, tile.workerID); }, - _addTile: function(tile) { - this.fire('tile.add', {tile: tile}); - }, - - _removeTile: function(tile) { - this.fire('tile.remove', {tile: tile}); - }, - - _unloadTile: function(tile) { + unloadTile: function(tile) { tile.unloadVectorData(this.map.painter); this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, null, tile.workerID); - }, - - redoPlacement: Source.redoPlacement, - - _redoTilePlacement: function(tile) { - tile.redoPlacement(this); } }); diff --git a/js/source/vector_tile_worker_source.js b/js/source/vector_tile_worker_source.js new file mode 100644 index 00000000000..b3e23ca2492 --- /dev/null +++ b/js/source/vector_tile_worker_source.js @@ -0,0 +1,128 @@ +'use strict'; +var ajax = require('../util/ajax'); +var vt = require('vector-tile'); +var Protobuf = require('pbf'); +var WorkerTile = require('./worker_tile'); + +module.exports = VectorTileWorkerSource; + +/** + * The {@link WorkerSource} implementation that supports {@link VectorTileSource}. + * This class is designed to be easily reused to support custom source types + * for data formats that can be parsed/converted into an in-memory VectorTile + * representation. To do so, create it with + * `new VectorTileWorkerSource(actor, styleLayers, customLoadVectorDataFunction)`. + * + * @class VectorTileWorkerSource + * @private + * @param {Function} [loadVectorData] Optional method for custom loading of a VectorTile object based on parameters passed from the main-thread Source. See {@link VectorTileWorkerSource#loadTile}. The default implementation simply loads the pbf at `params.url`. + */ +function VectorTileWorkerSource (actor, styleLayers, loadVectorData) { + this.actor = actor; + this.styleLayers = styleLayers; + + if (loadVectorData) { this.loadVectorData = loadVectorData; } + + this.loading = {}; + this.loaded = {}; +} + +VectorTileWorkerSource.prototype = { + /** + * Implements {@link WorkerSource#loadTile}. Delegates to {@link VectorTileWorkerSource#loadVectorData} (which by default expects a `params.url` property) for fetching and producing a VectorTile object. + * + * @param {object} params + * @param {string} params.source The id of the source for which we're loading this tile. + * @param {string} params.uid The UID for this tile. + * @param {TileCoord} params.coord + * @param {number} params.zoom + * @param {number} params.overscaling + * @param {number} params.angle + * @param {number} params.pitch + * @param {boolean} params.showCollisionBoxes + */ + loadTile: function(params, callback) { + var source = params.source, + uid = params.uid; + + if (!this.loading[source]) + this.loading[source] = {}; + + var tile = this.loading[source][uid] = new WorkerTile(params); + tile.abort = this.loadVectorData(params, done.bind(this)); + + function done(err, data) { + delete this.loading[source][uid]; + + if (err) return callback(err); + if (!data) return callback(null, null); + + tile.data = data.tile; + tile.parse(tile.data, this.styleLayers.getLayerFamilies(), this.actor, data.rawTileData, callback); + + this.loaded[source] = this.loaded[source] || {}; + this.loaded[source][uid] = tile; + } + }, + + /** + * Implements {@link WorkerSource#reloadTile}. + * + * @param {object} params + * @param {string} params.source The id of the source for which we're loading this tile. + * @param {string} params.uid The UID for this tile. + */ + reloadTile: function(params, callback) { + var loaded = this.loaded[params.source], + uid = params.uid; + if (loaded && loaded[uid]) { + var tile = loaded[uid]; + tile.parse(tile.data, this.styleLayers.getLayerFamilies(), this.actor, params.rawTileData, callback); + } + }, + + /** + * Implements {@link WorkerSource#abortTile}. + * + * @param {object} params + * @param {string} params.source The id of the source for which we're loading this tile. + * @param {string} params.uid The UID for this tile. + */ + abortTile: function(params) { + var loading = this.loading[params.source], + uid = params.uid; + if (loading && loading[uid] && loading[uid].abort) { + loading[uid].abort(); + delete loading[uid]; + } + }, + + /** + * Implements {@link WorkerSource#removeTile}. + * + * @param {object} params + * @param {string} params.source The id of the source for which we're loading this tile. + * @param {string} params.uid The UID for this tile. + */ + removeTile: function(params) { + var loaded = this.loaded[params.source], + uid = params.uid; + if (loaded && loaded[uid]) { + delete loaded[uid]; + } + }, + + /** + * @param {object} params + * @param {string} params.url The URL of the tile PBF to load. + */ + loadVectorData: function (params, callback) { + var xhr = ajax.getArrayBuffer(params.url, done.bind(this)); + return function abort () { xhr.abort(); }; + function done(err, data) { + if (err) { return callback(err); } + var tile = new vt.VectorTile(new Protobuf(new Uint8Array(data))); + callback(err, { tile: tile, rawTileData: data }); + } + } +}; diff --git a/js/source/video_source.js b/js/source/video_source.js index 7ef183c5ced..d2dc8dcf007 100644 --- a/js/source/video_source.js +++ b/js/source/video_source.js @@ -1,7 +1,6 @@ 'use strict'; var util = require('../util/util'); -var Tile = require('./tile'); var TileCoord = require('./tile_coord'); var LngLat = require('../geo/lng_lat'); var Point = require('point-geometry'); @@ -16,37 +15,42 @@ module.exports = VideoSource; /** * A data source containing video. - * - * @class VideoSource - * @param {Object} options - * @param {Array} options.urls An array of URLs to video files. - * @param {Array>} options.coordinates Four geographical coordinates, - * represented as arrays of longitude and latitude numbers, which define the corners of the video. - * The coordinates start at the top left corner of the video and proceed in clockwise order. - * They do not have to represent a rectangle. + * (See the [Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/#sources-video) for detailed documentation of options.) + * @interface VideoSource * @example - * var sourceObj = new mapboxgl.VideoSource({ + * // add to map + * map.addSource('some id', { + * type: 'video', * url: [ * 'https://www.mapbox.com/videos/baltimore-smoke.mp4', * 'https://www.mapbox.com/videos/baltimore-smoke.webm' * ], * coordinates: [ - * [-76.54335737228394, 39.18579907229748], - * [-76.52803659439087, 39.1838364847587], - * [-76.5295386314392, 39.17683392507606], - * [-76.54520273208618, 39.17876344106642] + * [-76.54, 39.18], + * [-76.52, 39.18], + * [-76.52, 39.17], + * [-76.54, 39.17] * ] * }); - * map.addSource('some id', sourceObj); // add + * + * // update + * var mySource = map.getSource('some id'); + * mySource.setCoordinates([ + * [-76.54335737228394, 39.18579907229748], + * [-76.52803659439087, 39.1838364847587], + * [-76.5295386314392, 39.17683392507606], + * [-76.54520273208618, 39.17876344106642] + * ]); + * * map.removeSource('some id'); // remove */ -function VideoSource(options) { +function VideoSource(id, options) { + this.id = id; this.urls = options.urls; this.coordinates = options.coordinates; ajax.getVideo(options.urls, function(err, video) { - // @TODO handle errors via event. - if (err) return; + if (err) return this.fire('error', {error: err}); this.video = video; this.video.loop = true; @@ -64,16 +68,19 @@ function VideoSource(options) { this.map.style.animationLoop.cancel(loopID); }.bind(this)); - this._loaded = true; - if (this.map) { this.video.play(); this.setCoordinates(options.coordinates); } + + this.fire('load'); }.bind(this)); } VideoSource.prototype = util.inherit(Evented, /** @lends VideoSource.prototype */{ + minzoom: 0, + maxzoom: 22, + tileSize: 512, roundZoom: true, /** @@ -86,6 +93,7 @@ VideoSource.prototype = util.inherit(Evented, /** @lends VideoSource.prototype * }, onAdd: function(map) { + if (this.map) return; this.map = map; if (this.video) { this.video.play(); @@ -118,50 +126,43 @@ VideoSource.prototype = util.inherit(Evented, /** @lends VideoSource.prototype * centerCoord.column = Math.round(centerCoord.column); centerCoord.row = Math.round(centerCoord.row); - - var tileCoords = cornerZ0Coords.map(function(coord) { + this.minzoom = this.maxzoom = centerCoord.zoom; + this._coord = new TileCoord(centerCoord.zoom, centerCoord.column, centerCoord.row); + this._tileCoords = cornerZ0Coords.map(function(coord) { var zoomedCoord = coord.zoomTo(centerCoord.zoom); return new Point( Math.round((zoomedCoord.column - centerCoord.column) * EXTENT), Math.round((zoomedCoord.row - centerCoord.row) * EXTENT)); }); + this.fire('change'); + return this; + }, + + _setTile: function (tile) { + this._prepared = false; + this.tile = tile; var maxInt16 = 32767; var array = new RasterBoundsArray(); - array.emplaceBack(tileCoords[0].x, tileCoords[0].y, 0, 0); - array.emplaceBack(tileCoords[1].x, tileCoords[1].y, maxInt16, 0); - array.emplaceBack(tileCoords[3].x, tileCoords[3].y, 0, maxInt16); - array.emplaceBack(tileCoords[2].x, tileCoords[2].y, maxInt16, maxInt16); + array.emplaceBack(this._tileCoords[0].x, this._tileCoords[0].y, 0, 0); + array.emplaceBack(this._tileCoords[1].x, this._tileCoords[1].y, maxInt16, 0); + array.emplaceBack(this._tileCoords[3].x, this._tileCoords[3].y, 0, maxInt16); + array.emplaceBack(this._tileCoords[2].x, this._tileCoords[2].y, maxInt16, maxInt16); - this.tile = new Tile(new TileCoord(centerCoord.zoom, centerCoord.column, centerCoord.row)); this.tile.buckets = {}; this.tile.boundsBuffer = new Buffer(array.serialize(), RasterBoundsArray.serialize(), Buffer.BufferType.VERTEX); this.tile.boundsVAO = new VertexArrayObject(); - - this.fire('change'); - - return this; - }, - - loaded: function() { - return this.video && this.video.readyState >= 2; - }, - - update: function() { - // noop - }, - - reload: function() { - // noop + this.tile.state = 'loaded'; }, prepare: function() { - if (!this._loaded) return; if (this.video.readyState < 2) return; // not enough data for current position + if (!this.tile) return; var gl = this.map.painter.gl; - if (!this.tile.texture) { + if (!this._prepared) { + this._prepared = true; this.tile.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.tile.texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); @@ -177,13 +178,18 @@ VideoSource.prototype = util.inherit(Evented, /** @lends VideoSource.prototype * this._currentTime = this.video.currentTime; }, - getVisibleCoordinates: function() { - if (this.tile) return [this.tile.coord]; - else return []; - }, - - getTile: function() { - return this.tile; + loadTile: function(tile, callback) { + // We have a single tile -- whoose coordinates are this._coord -- that + // covers the video frame we want to render. If that's the one being + // requested, set it up with the image; otherwise, mark the tile as + // `errored` to indicate that we have no data for it. + if (this._coord && this._coord.toString() === tile.coord.toString()) { + this._setTile(tile); + callback(null); + } else { + tile.state = 'errored'; + callback(null); + } }, serialize: function() { diff --git a/js/source/worker.js b/js/source/worker.js index bb0fc4863b9..5aeebfd3464 100644 --- a/js/source/worker.js +++ b/js/source/worker.js @@ -1,18 +1,11 @@ 'use strict'; var Actor = require('../util/actor'); -var WorkerTile = require('./worker_tile'); var StyleLayer = require('../style/style_layer'); var util = require('../util/util'); -var ajax = require('../util/ajax'); -var vt = require('vector-tile'); -var Protobuf = require('pbf'); -var supercluster = require('supercluster'); -var geojsonvt = require('geojson-vt'); -var rewind = require('geojson-rewind'); -var GeoJSONWrapper = require('./geojson_wrapper'); -var vtpbf = require('vt-pbf'); +var VectorTileWorkerSource = require('./vector_tile_worker_source'); +var GeoJSONWorkerSource = require('./geojson_worker_source'); module.exports = function(self) { return new Worker(self); @@ -21,10 +14,24 @@ module.exports = function(self) { function Worker(self) { this.self = self; this.actor = new Actor(self, this); - this.loading = {}; - this.loaded = {}; - this.geoJSONIndexes = {}; + // simple accessor object for passing to WorkerSources + var styleLayers = { + getLayers: function () { return this.layers; }.bind(this), + getLayerFamilies: function () { return this.layerFamilies; }.bind(this) + }; + + this.workerSources = { + vector: new VectorTileWorkerSource(this.actor, styleLayers), + geojson: new GeoJSONWorkerSource(this.actor, styleLayers) + }; + + this.self.registerWorkerSource = function (name, WorkerSource) { + if (this.workerSources[name]) { + throw new Error('Worker source with name "' + name + '" already registered.'); + } + this.workerSources[name] = new WorkerSource(this.actor, styleLayers); + }.bind(this); } util.extend(Worker.prototype, { @@ -93,128 +100,42 @@ util.extend(Worker.prototype, { }, 'load tile': function(params, callback) { - var source = params.source, - uid = params.uid; - - if (!this.loading[source]) - this.loading[source] = {}; - - - var tile = this.loading[source][uid] = new WorkerTile(params); - - tile.xhr = ajax.getArrayBuffer(params.url, done.bind(this)); - - function done(err, data) { - delete this.loading[source][uid]; - - if (err) return callback(err); - - tile.data = new vt.VectorTile(new Protobuf(new Uint8Array(data))); - tile.parse(tile.data, this.layerFamilies, this.actor, data, callback); - - this.loaded[source] = this.loaded[source] || {}; - this.loaded[source][uid] = tile; - } + var type = params.type || 'vector'; + this.workerSources[type].loadTile(params, callback); }, 'reload tile': function(params, callback) { - var loaded = this.loaded[params.source], - uid = params.uid; - if (loaded && loaded[uid]) { - var tile = loaded[uid]; - tile.parse(tile.data, this.layerFamilies, this.actor, params.rawTileData, callback); - } + var type = params.type || 'vector'; + this.workerSources[type].reloadTile(params, callback); }, 'abort tile': function(params) { - var loading = this.loading[params.source], - uid = params.uid; - if (loading && loading[uid]) { - loading[uid].xhr.abort(); - delete loading[uid]; - } + var type = params.type || 'vector'; + this.workerSources[type].abortTile(params); }, 'remove tile': function(params) { - var loaded = this.loaded[params.source], - uid = params.uid; - if (loaded && loaded[uid]) { - delete loaded[uid]; - } + var type = params.type || 'vector'; + this.workerSources[type].removeTile(params); }, 'redo placement': function(params, callback) { - var loaded = this.loaded[params.source], - loading = this.loading[params.source], - uid = params.uid; - - if (loaded && loaded[uid]) { - var tile = loaded[uid]; - var result = tile.redoPlacement(params.angle, params.pitch, params.showCollisionBoxes); - - if (result.result) { - callback(null, result.result, result.transferables); - } - - } else if (loading && loading[uid]) { - loading[uid].angle = params.angle; - } + var type = params.type || 'vector'; + this.workerSources[type].redoPlacement(params, callback); }, - 'parse geojson': function(params, callback) { - var indexData = function(err, data) { - rewind(data, true); - if (err) return callback(err); - if (typeof data != 'object') { - return callback(new Error("Input data is not a valid GeoJSON object.")); - } - try { - this.geoJSONIndexes[params.source] = params.cluster ? - supercluster(params.superclusterOptions).load(data.features) : - geojsonvt(data, params.geojsonVtOptions); - } catch (err) { - return callback(err); - } - callback(null); - }.bind(this); - - // Not, because of same origin issues, urls must either include an - // explicit origin or absolute path. - // ie: /foo/bar.json or http://example.com/bar.json - // but not ../foo/bar.json - if (params.url) { - ajax.getJSON(params.url, indexData); - } else if (typeof params.data === 'string') { - indexData(null, JSON.parse(params.data)); - } else { - return callback(new Error("Input data is not a valid GeoJSON object.")); - } - }, - - 'load geojson tile': function(params, callback) { - var source = params.source, - coord = params.coord; - - if (!this.geoJSONIndexes[source]) return callback(null, null); // we couldn't load the file - - var geoJSONTile = this.geoJSONIndexes[source].getTile(Math.min(coord.z, params.maxZoom), coord.x, coord.y); - - var tile = geoJSONTile ? new WorkerTile(params) : undefined; - - this.loaded[source] = this.loaded[source] || {}; - this.loaded[source][params.uid] = tile; - - if (geoJSONTile) { - var geojsonWrapper = new GeoJSONWrapper(geoJSONTile.features); - geojsonWrapper.name = '_geojsonTileLayer'; - var pbf = vtpbf({ layers: { '_geojsonTileLayer': geojsonWrapper }}); - if (pbf.byteOffset !== 0 || pbf.byteLength !== pbf.buffer.byteLength) { - // Compatibility with node Buffer (https://github.com/mapbox/pbf/issues/35) - pbf = new Uint8Array(pbf); - } - tile.parse(geojsonWrapper, this.layerFamilies, this.actor, pbf.buffer, callback); - } else { - return callback(null, null); // nothing in the given tile + /** + * Load a {@link WorkerSource} script at params.url. The script is run + * (using importScripts) with `registerWorkerSource` in scope, which is a + * function taking `(name, workerSourceObject)`. + * @private + */ + 'load worker source': function(params, callback) { + try { + this.self.importScripts(params.url); + callback(); + } catch (e) { + callback(e); } } }); @@ -239,3 +160,4 @@ function createLayerFamilies(layers) { return families; } + diff --git a/js/source/worker_tile.js b/js/source/worker_tile.js index f7e70e299ba..68aaa4bd920 100644 --- a/js/source/worker_tile.js +++ b/js/source/worker_tile.js @@ -35,8 +35,6 @@ WorkerTile.prototype.parse = function(data, layerFamilies, actor, rawTileData, c var featureIndex = new FeatureIndex(this.coord, this.overscaling, collisionTile, data.layers); var sourceLayerCoder = new DictionaryCoder(data.layers ? Object.keys(data.layers).sort() : ['_geojsonTileLayer']); - var stats = { _total: 0 }; - var tile = this; var bucketsById = {}; var bucketsBySourceLayer = {}; @@ -184,9 +182,7 @@ WorkerTile.prototype.parse = function(data, layerFamilies, actor, rawTileData, c } function parseBucket(tile, bucket) { - var now = Date.now(); bucket.populateArrays(collisionTile, stacks, icons); - var time = Date.now() - now; if (bucket.type !== 'symbol') { @@ -197,9 +193,6 @@ WorkerTile.prototype.parse = function(data, layerFamilies, actor, rawTileData, c } bucket.features = null; - - stats._total += time; - stats[bucket.id] = (stats[bucket.id] || 0) + time; } function done() { @@ -220,7 +213,6 @@ WorkerTile.prototype.parse = function(data, layerFamilies, actor, rawTileData, c callback(null, { buckets: nonEmptyBuckets.map(serializeBucket), - bucketStats: stats, featureIndex: featureIndex_.data, collisionTile: collisionTile_.data, collisionBoxArray: collisionBoxArray, diff --git a/js/style/style.js b/js/style/style.js index 193b265f145..d6a30f90013 100644 --- a/js/style/style.js +++ b/js/style/style.js @@ -14,6 +14,8 @@ var Dispatcher = require('../util/dispatcher'); var AnimationLoop = require('./animation_loop'); var validateStyle = require('./validate_style'); var Source = require('../source/source'); +var QueryFeatures = require('../source/query_features'); +var SourceCache = require('../source/source_cache'); var styleSpec = require('./style_spec'); var StyleFunction = require('./style_function'); @@ -40,7 +42,7 @@ function Style(stylesheet, animationLoop, workerCount) { this._resetUpdates(); - var loaded = function(err, stylesheet) { + var stylesheetLoaded = function(err, stylesheet) { if (err) { this.fire('error', {error: err}); return; @@ -69,9 +71,9 @@ function Style(stylesheet, animationLoop, workerCount) { }.bind(this); if (typeof stylesheet === 'string') { - ajax.getJSON(normalizeURL(stylesheet), loaded); + ajax.getJSON(normalizeURL(stylesheet), stylesheetLoaded); } else { - browser.frame(loaded.bind(this, null, stylesheet)); + browser.frame(stylesheetLoaded.bind(this, null, stylesheet)); } this.on('source.load', function(event) { @@ -321,13 +323,16 @@ Style.prototype = util.inherit(Evented, { if (this.sources[id] !== undefined) { throw new Error('There is already a source with this ID'); } - if (!Source.is(source) && this._handleErrors(validateStyle.source, 'sources.' + id, source)) return this; + if (!source.type) { + throw new Error('The type property must be defined, but the only the following properties were given: ' + Object.keys(source) + '.'); + } + var builtIns = ['vector', 'raster', 'geojson', 'video', 'image']; + var shouldValidate = builtIns.indexOf(source.type) >= 0; + if (shouldValidate && this._handleErrors(validateStyle.source, 'sources.' + id, source)) return this; - source = Source.create(source); + source = new SourceCache(id, source, this.dispatcher); this.sources[id] = source; - source.id = id; source.style = this; - source.dispatcher = this.dispatcher; source .on('load', this._forwardSourceEvent) .on('error', this._forwardSourceEvent) @@ -382,7 +387,7 @@ Style.prototype = util.inherit(Evented, { * @private */ getSource: function(id) { - return this.sources[id]; + return this.sources[id] && this.sources[id].getSource(); }, /** @@ -649,9 +654,8 @@ Style.prototype = util.inherit(Evented, { for (var id in this.sources) { if (params.layers && !includedSources[id]) continue; var source = this.sources[id]; - if (source.queryRenderedFeatures) { - sourceResults.push(source.queryRenderedFeatures(queryGeometry, params, zoom, bearing)); - } + var results = QueryFeatures.rendered(source, this._layers, queryGeometry, params, zoom, bearing); + sourceResults.push(results); } return this._flattenRenderedFeatures(sourceResults); }, @@ -660,8 +664,25 @@ Style.prototype = util.inherit(Evented, { if (params && params.filter) { this._handleErrors(validateStyle.filter, 'querySourceFeatures.filter', params.filter, true); } - var source = this.getSource(sourceID); - return source && source.querySourceFeatures ? source.querySourceFeatures(params) : []; + var source = this.sources[sourceID]; + return source ? QueryFeatures.source(source, params) : []; + }, + + addSourceType: function (name, SourceType, callback) { + if (Source.getType(name)) { + return callback(new Error('A source type called "' + name + '" already exists.')); + } + + Source.setType(name, SourceType); + + if (!SourceType.workerSourceURL) { + return callback(null, null); + } + + this.dispatcher.broadcast('load worker source', { + name: name, + url: SourceType.workerSourceURL + }, callback); }, _handleErrors: function(validate, key, value, throws, props) { @@ -696,7 +717,7 @@ Style.prototype = util.inherit(Evented, { }, _forwardSourceEvent: function(e) { - this.fire('source.' + e.type, util.extend({source: e.target}, e)); + this.fire('source.' + e.type, util.extend({source: e.target.getSource()}, e)); }, _forwardTileEvent: function(e) { @@ -754,3 +775,4 @@ Style.prototype = util.inherit(Evented, { } } }); + diff --git a/js/ui/map.js b/js/ui/map.js index 85440fd6735..19734dccf47 100755 --- a/js/ui/map.js +++ b/js/ui/map.js @@ -669,6 +669,7 @@ util.extend(Map.prototype, /** @lends Map.prototype */{ * @param {string} id The ID of the source to add. Must not conflict with existing sources. * @param {Object} source The source object, conforming to the * Mapbox Style Specification's [source definition](https://www.mapbox.com/mapbox-gl-style-spec/#sources). + * @param {string} source.type The source type, which must be either one of the core Mapbox GL source types defined in the style specification or a custom type that has been added to the map with {@link Map#addSourceType}. * @fires source.add * @returns {Map} `this` */ @@ -678,6 +679,18 @@ util.extend(Map.prototype, /** @lends Map.prototype */{ return this; }, + /** + * Adds a [custom source type](#Custom Sources), making it available for use with + * {@link Map#addSource}. + * @private + * @param {string} name The name of the source type; source definition objects use this name in the `{type: ...}` field. + * @param {Function} SourceType A {@link Source} constructor. + * @param {Function} callback Called when the source type is ready or with an error argument if there is an error. + */ + addSourceType: function (name, SourceType, callback) { + return this.style.addSourceType(name, SourceType, callback); + }, + /** * Removes a source from the map's style. * diff --git a/js/util/actor.js b/js/util/actor.js index ef22df5e502..dc09086cfeb 100644 --- a/js/util/actor.js +++ b/js/util/actor.js @@ -23,25 +23,32 @@ function Actor(target, parent) { Actor.prototype.receive = function(message) { var data = message.data, + id = data.id, callback; if (data.type === '') { callback = this.callbacks[data.id]; delete this.callbacks[data.id]; - callback(data.error || null, data.data); - } else if (typeof data.id !== 'undefined') { - var id = data.id; - this.parent[data.type](data.data, function(err, data, buffers) { - this.postMessage({ - type: '', - id: String(id), - error: err ? String(err) : null, - data: data - }, buffers); - }.bind(this)); + if (callback) callback(data.error || null, data.data); + } else if (typeof data.id !== 'undefined' && this.parent[data.type]) { + // data.type == 'load tile', 'remove tile', etc. + this.parent[data.type](data.data, done.bind(this)); + } else if (typeof data.id !== 'undefined' && this.parent.workerSources) { + // data.type == sourcetype.method + var keys = data.type.split('.'); + this.parent.workerSources[keys[0]][keys[1]](data.data, done.bind(this)); } else { this.parent[data.type](data.data); } + + function done(err, data, buffers) { + this.postMessage({ + type: '', + id: String(id), + error: err ? String(err) : null, + data: data + }, buffers); + } }; Actor.prototype.send = function(type, data, callback, buffers) { diff --git a/js/util/browser/dispatcher.js b/js/util/browser/dispatcher.js index 29ef27043fa..605d5b3bbdb 100644 --- a/js/util/browser/dispatcher.js +++ b/js/util/browser/dispatcher.js @@ -1,10 +1,18 @@ 'use strict'; +var util = require('../util'); var Actor = require('../actor'); var WebWorkify = require('webworkify'); module.exports = Dispatcher; +/** + * Responsible for sending messages from a {@link Source} to an associated + * {@link WorkerSource}. + * + * @interface Dispatcher + * @private + */ function Dispatcher(length, parent) { this.actors = []; this.currentActor = 0; @@ -17,12 +25,35 @@ function Dispatcher(length, parent) { } Dispatcher.prototype = { - broadcast: function(type, data) { - for (var i = 0; i < this.actors.length; i++) { - this.actors[i].send(type, data); - } + /** + * Broadcast a message to all Workers. + * @method + * @name broadcast + * @param {string} type + * @param {object} data + * @param {Function} callback + * @memberof Dispatcher + * @instance + */ + broadcast: function(type, data, cb) { + cb = cb || function () {}; + util.asyncAll(this.actors, function (actor, done) { + actor.send(type, data, done); + }, cb); }, + /** + * Send a message to a Worker. + * @method + * @name send + * @param {string} type + * @param {object} data + * @param {Function} callback + * @param {number|undefined} [targetID] The ID of the Worker to which to send this message. Omit to allow the dispatcher to choose. + * @returns {number} The ID of the worker to which the message was sent. + * @memberof Dispatcher + * @instance + */ send: function(type, data, callback, targetID, buffers) { if (typeof targetID !== 'number' || isNaN(targetID)) { // Use round robin to send requests to web workers. diff --git a/js/util/dispatcher.js b/js/util/dispatcher.js index 781240831d3..3406eaef8f8 100644 --- a/js/util/dispatcher.js +++ b/js/util/dispatcher.js @@ -33,6 +33,9 @@ function Dispatcher(length, parent) { parentBus.target = workerBus; workerBus.target = parentBus; + // workerBus substitutes the WebWorker global `self`, and Worker uses + // self.importScripts for the 'load worker source' target. + workerBus.importScripts = function () {}; this.worker = new Worker(workerBus); this.actor = new Actor(parentBus, parent); @@ -44,8 +47,8 @@ function Dispatcher(length, parent) { } Dispatcher.prototype = { - broadcast: function(type, data) { - this.actor.send(type, data); + broadcast: function(type, data, callback) { + this.actor.send(type, data, callback); }, send: function(type, data, callback, targetID, buffers) { diff --git a/js/util/util.js b/js/util/util.js index f4f769642e4..ac627194b79 100644 --- a/js/util/util.js +++ b/js/util/util.js @@ -268,6 +268,7 @@ exports.debounce = function(fn, time) { */ exports.bindAll = function(fns, context) { fns.forEach(function(fn) { + if (!context[fn]) { return; } context[fn] = context[fn].bind(context); }); }; diff --git a/package.json b/package.json index 7275795c433..2ccbeb7fc88 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#df624912cd514b89dc4405b233fc25db85bbb8b7", "minifyify": "^7.0.1", "nyc": "6.4.0", + "proxyquire": "^1.7.9", "remark": "4.2.2", "remark-html": "3.0.0", "sinon": "^1.15.4", diff --git a/server.js b/server.js index 1ad8b43a127..acd8eaef7ea 100644 --- a/server.js +++ b/server.js @@ -41,6 +41,10 @@ app.get('/debug', function(req, res) { app.use(express.static(path.join(__dirname, 'debug'))); app.use('/dist', express.static(path.join(__dirname, 'dist'))); +// serve files in mapbox-gl-test-suite, so that debug files can use its +// data/image/video/etc. assets +app.use('/mapbox-gl-test-suite', express.static(path.join(__dirname, 'node_modules/mapbox-gl-test-suite'))); + downloadBenchData(function() { app.listen(9966, function () { console.log('mapbox-gl-js debug server running at http://localhost:9966'); diff --git a/test/js/geo/transform.test.js b/test/js/geo/transform.test.js index a0ff6163af9..46be14bbe37 100644 --- a/test/js/geo/transform.test.js +++ b/test/js/geo/transform.test.js @@ -3,6 +3,7 @@ var test = require('tap').test; var Point = require('point-geometry'); var Transform = require('../../../js/geo/transform'); +var TileCoord = require('../../../js/source/tile_coord'); var LngLat = require('../../../js/geo/lng_lat'); var fixed = require('../../testutil/fixed'); @@ -105,5 +106,94 @@ test('transform', function(t) { t.end(); }); + test('coveringTiles', function(t) { + var options = { + minzoom: 1, + maxzoom: 10, + tileSize: 512 + }; + + var transform = new Transform(); + transform.resize(200, 200); + + transform.zoom = 0; + t.deepEqual(transform.coveringTiles(options), []); + + transform.zoom = 1; + t.deepEqual(transform.coveringTiles(options), ['1', '33', '65', '97'].map(TileCoord.fromID)); + + transform.zoom = 2.4; + t.deepEqual(transform.coveringTiles(options), ['162', '194', '290', '322'].map(TileCoord.fromID)); + + transform.zoom = 10; + t.deepEqual(transform.coveringTiles(options), ['16760810', '16760842', '16793578', '16793610'].map(TileCoord.fromID)); + + transform.zoom = 11; + t.deepEqual(transform.coveringTiles(options), ['16760810', '16760842', '16793578', '16793610'].map(TileCoord.fromID)); + + t.end(); + }); + + test('coveringZoomLevel', function(t) { + var options = { + minzoom: 1, + maxzoom: 10, + tileSize: 512 + }; + + var transform = new Transform(); + + transform.zoom = 0; + t.deepEqual(transform.coveringZoomLevel(options), 0); + + transform.zoom = 0.1; + t.deepEqual(transform.coveringZoomLevel(options), 0); + + transform.zoom = 1; + t.deepEqual(transform.coveringZoomLevel(options), 1); + + transform.zoom = 2.4; + t.deepEqual(transform.coveringZoomLevel(options), 2); + + transform.zoom = 10; + t.deepEqual(transform.coveringZoomLevel(options), 10); + + transform.zoom = 11; + t.deepEqual(transform.coveringZoomLevel(options), 11); + + transform.zoom = 11.5; + t.deepEqual(transform.coveringZoomLevel(options), 11); + + options.tileSize = 256; + + transform.zoom = 0; + t.deepEqual(transform.coveringZoomLevel(options), 1); + + transform.zoom = 0.1; + t.deepEqual(transform.coveringZoomLevel(options), 1); + + transform.zoom = 1; + t.deepEqual(transform.coveringZoomLevel(options), 2); + + transform.zoom = 2.4; + t.deepEqual(transform.coveringZoomLevel(options), 3); + + transform.zoom = 10; + t.deepEqual(transform.coveringZoomLevel(options), 11); + + transform.zoom = 11; + t.deepEqual(transform.coveringZoomLevel(options), 12); + + transform.zoom = 11.5; + t.deepEqual(transform.coveringZoomLevel(options), 12); + + options.roundZoom = true; + + t.deepEqual(transform.coveringZoomLevel(options), 13); + + t.end(); + }); + + t.end(); }); diff --git a/test/js/source/geojson_source.test.js b/test/js/source/geojson_source.test.js index e5cf6042d1a..08596980a07 100644 --- a/test/js/source/geojson_source.test.js +++ b/test/js/source/geojson_source.test.js @@ -1,10 +1,16 @@ 'use strict'; var test = require('tap').test; +var Tile = require('../../../js/source/tile'); +var TileCoord = require('../../../js/source/tile_coord'); var GeoJSONSource = require('../../../js/source/geojson_source'); var Transform = require('../../../js/geo/transform'); var LngLat = require('../../../js/geo/lng_lat'); +var mockDispatcher = { + send: function () {} +}; + var hawkHill = { "type": "FeatureCollection", "features": [{ @@ -41,13 +47,18 @@ var hawkHill = { test('GeoJSONSource#setData', function(t) { t.test('returns self', function(t) { - var source = new GeoJSONSource({data: {}}); + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); t.equal(source.setData({}), source); t.end(); }); t.test('fires change', function(t) { - var source = new GeoJSONSource({data: {}}); + var mockDispatcher = { + send: function (type, data, callback) { + return callback(); + } + }; + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); source.on('change', function() { t.end(); }); @@ -57,20 +68,6 @@ test('GeoJSONSource#setData', function(t) { t.end(); }); -test('GeoJSONSource#reload', function(t) { - t.test('before loaded', function(t) { - var source = new GeoJSONSource({data: {}}); - - t.doesNotThrow(function() { - source.reload(); - }, null, 'reload ignored gracefully'); - - t.end(); - }); - - t.end(); -}); - test('GeoJSONSource#update', function(t) { var transform = new Transform(); transform.resize(200, 200); @@ -79,30 +76,22 @@ test('GeoJSONSource#update', function(t) { transform.zoom = 15; transform.setLocationAtPoint(lngLat, point); - t.test('sends parse request to dispatcher', function(t) { - var source = new GeoJSONSource({data: {}}); - - source.dispatcher = { + t.test('sends initial loadData request to dispatcher', function(t) { + var mockDispatcher = { send: function(message) { - t.equal(message, 'parse geojson'); + t.equal(message, 'geojson.loadData'); t.end(); } }; - source.update(transform); + /* eslint-disable no-new */ + new GeoJSONSource('id', {data: {}}, mockDispatcher); }); t.test('forwards geojson-vt options with worker request', function(t) { - var source = new GeoJSONSource({ - data: {}, - maxzoom: 10, - tolerance: 0.25, - buffer: 16 - }); - - source.dispatcher = { + var mockDispatcher = { send: function(message, params) { - t.equal(message, 'parse geojson'); + t.equal(message, 'geojson.loadData'); t.deepEqual(params.geojsonVtOptions, { extent: 8192, maxZoom: 10, @@ -113,35 +102,36 @@ test('GeoJSONSource#update', function(t) { } }; - source.update(transform); + new GeoJSONSource('id', { + data: {}, + maxzoom: 10, + tolerance: 0.25, + buffer: 16 + }, mockDispatcher); }); - t.test('emits change on success', function(t) { - var source = new GeoJSONSource({data: {}}); - - source.dispatcher = { + t.test('emits load on success', function(t) { + var mockDispatcher = { send: function(message, args, callback) { setTimeout(callback, 0); } }; - source.update(transform); + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); - source.on('change', function() { + source.on('load', function() { t.end(); }); }); t.test('emits error on failure', function(t) { - var source = new GeoJSONSource({data: {}}); - - source.dispatcher = { + var mockDispatcher = { send: function(message, args, callback) { setTimeout(callback.bind(null, 'error'), 0); } }; - source.update(transform); + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); source.on('error', function(err) { t.equal(err.error, 'error'); @@ -149,38 +139,45 @@ test('GeoJSONSource#update', function(t) { }); }); - t.test('clears previous tiles', function(t) { - var source = new GeoJSONSource({data: hawkHill}); - - source.used = true; - source.dispatcher = { + t.test('emits change on data update', function(t) { + var mockDispatcher = { send: function(message, args, callback) { setTimeout(callback, 0); } }; - source.map = { - options: { - maxZoom: 20 - }, - transform: new Transform() - }; - source.map.transform.resize(512, 512); + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); - source.style = {}; + source.on('load', function() { + // Note: we register this before calling setData because `change` + // is fired synchronously within that method. It may be worth + // considering dezalgoing there. + source.on('change', function () { + t.end(); + }); + source.setData({}); + }); + }); - source.update(transform); + t.test('sends loadData request to dispatcher after data update', function(t) { + var expectedLoadDataCalls = 2; + var mockDispatcher = { + send: function(message, args, callback) { + if (message === 'geojson.loadData' && --expectedLoadDataCalls <= 0) { + t.end(); + } + setTimeout(callback, 0); + } + }; - source.once('change', function() { - source.update(transform); // Load tiles + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); + source.map = { + transform: {} + }; + source.on('load', function () { source.setData({}); - source.update(transform); - - source.once('change', function() { - t.deepEqual(source._pyramid.getRenderableIds(), []); - t.end(); - }); + source.loadTile(new Tile(new TileCoord(0, 0, 0), 512), function () {}); }); }); @@ -190,7 +187,7 @@ test('GeoJSONSource#update', function(t) { test('GeoJSONSource#serialize', function(t) { t.test('serialize source with inline data', function(t) { - var source = new GeoJSONSource({data: hawkHill}); + var source = new GeoJSONSource('id', {data: hawkHill}, mockDispatcher); t.deepEqual(source.serialize(), { type: 'geojson', data: hawkHill @@ -199,7 +196,7 @@ test('GeoJSONSource#serialize', function(t) { }); t.test('serialize source with url', function(t) { - var source = new GeoJSONSource({data: 'local://data.json'}); + var source = new GeoJSONSource('id', {data: 'local://data.json'}, mockDispatcher); t.deepEqual(source.serialize(), { type: 'geojson', data: 'local://data.json' @@ -208,7 +205,7 @@ test('GeoJSONSource#serialize', function(t) { }); t.test('serialize source with updated data', function(t) { - var source = new GeoJSONSource({data: {}}); + var source = new GeoJSONSource('id', {data: {}}, mockDispatcher); source.setData(hawkHill); t.deepEqual(source.serialize(), { type: 'geojson', @@ -220,11 +217,3 @@ test('GeoJSONSource#serialize', function(t) { t.end(); }); -test('GeoJSONSource#queryRenderedFeatures', function(t) { - t.test('returns an empty object before loaded', function(t) { - var source = new GeoJSONSource({data: {}}); - t.deepEqual(source.queryRenderedFeatures(), []); - t.end(); - }); - t.end(); -}); diff --git a/test/js/source/query_features.test.js b/test/js/source/query_features.test.js new file mode 100644 index 00000000000..11b86c7c5aa --- /dev/null +++ b/test/js/source/query_features.test.js @@ -0,0 +1,15 @@ +'use strict'; + +var test = require('tap').test; +var QueryFeatures = require('../../../js/source/query_features.js'); + +test('QueryFeatures#rendered', function (t) { + t.test('returns empty object if source returns no tiles', function (t) { + var mockSourceCache = { tilesIn: function () { return []; } }; + var result = QueryFeatures.rendered(mockSourceCache); + t.deepEqual(result, []); + t.end(); + }); + + t.end(); +}); diff --git a/test/js/source/source_cache.test.js b/test/js/source/source_cache.test.js new file mode 100644 index 00000000000..7e3d1c3b880 --- /dev/null +++ b/test/js/source/source_cache.test.js @@ -0,0 +1,746 @@ +'use strict'; + +var test = require('tap').test; +var SourceCache = require('../../../js/source/source_cache'); +var Source = require('../../../js/source/source'); +var TileCoord = require('../../../js/source/tile_coord'); +var Transform = require('../../../js/geo/transform'); +var LngLat = require('../../../js/geo/lng_lat'); +var Coordinate = require('../../../js/geo/coordinate'); +var Evented = require('../../../js/util/evented'); +var util = require('../../../js/util/util'); + +// Add a mocked source type for use in these tests +Source.setType('mock-source-type', function create (id, sourceOptions) { + // allow tests to override mocked methods/properties by providing + // them in the source definition object that's given to Source.create() + var source = util.extend({ + id: id, + minzoom: 0, + maxzoom: 22, + loadTile: function (tile, callback) { + setTimeout(callback, 0); + }, + abortTile: function () {}, + unloadTile: function () {}, + serialize: function () {} + }, sourceOptions); + source = util.inherit(Evented, source); + + if (sourceOptions.noLoad) { return source; } + setTimeout(function () { + if (sourceOptions.error) { + source.fire('error', { error: sourceOptions.error }); + } else { + source.fire('load'); + } + }, 0); + return source; +}); + +function createSourceCache(options, used) { + var sc = new SourceCache('id', util.extend({ + tileSize: 512, + minzoom: 0, + maxzoom: 14, + type: 'mock-source-type' + }, options), /* dispatcher */ {}); + sc.used = typeof used === 'boolean' ? used : true; + return sc; +} + +test('SourceCache#addTile', function(t) { + t.test('loads tile when uncached', function(t) { + var coord = new TileCoord(0, 0, 0); + var sourceCache = createSourceCache({ + loadTile: function(tile) { + t.deepEqual(tile.coord, coord); + t.equal(tile.uses, 0); + t.end(); + } + }); + sourceCache.addTile(coord); + }); + + t.test('adds tile when uncached', function(t) { + var coord = new TileCoord(0, 0, 0); + var sourceCache = createSourceCache({}) + .on('tile.add', function (data) { + t.deepEqual(data.tile.coord, coord); + t.equal(data.tile.uses, 1); + t.end(); + }); + sourceCache.addTile(coord); + }); + + t.test('uses cached tile', function(t) { + var coord = new TileCoord(0, 0, 0), + load = 0, + add = 0; + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.state = 'loaded'; + load++; + callback(); + } + }) + .on('tile.add', function () { add++; }); + + var tr = new Transform(); + tr.width = 512; + tr.height = 512; + sourceCache.updateCacheSize(tr); + sourceCache.addTile(coord); + sourceCache.removeTile(coord.id); + sourceCache.addTile(coord); + + t.equal(load, 1); + t.equal(add, 2); + + t.end(); + }); + + t.test('reuses wrapped tile', function(t) { + var coord = new TileCoord(0, 0, 0), + load = 0, + add = 0; + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.state = 'loaded'; + load++; + callback(); + } + }) + .on('tile.add', function () { add++; }); + + var t1 = sourceCache.addTile(coord); + var t2 = sourceCache.addTile(new TileCoord(0, 0, 0, 1)); + + t.equal(load, 1); + t.equal(add, 2); + t.equal(t1, t2); + + t.end(); + }); + + t.end(); +}); + +test('SourceCache#removeTile', function(t) { + t.test('removes tile', function(t) { + var coord = new TileCoord(0, 0, 0); + var sourceCache = createSourceCache({}) + .on('tile.remove', function (data) { + var tile = data.tile; + t.deepEqual(tile.coord, coord); + t.equal(tile.uses, 0); + t.end(); + }); + sourceCache.addTile(coord); + sourceCache.removeTile(coord.id); + }); + + t.test('caches (does not unload) loaded tile', function(t) { + var coord = new TileCoord(0, 0, 0); + var sourceCache = createSourceCache({ + loadTile: function(tile) { + tile.state = 'loaded'; + }, + unloadTile: function() { + t.fail(); + } + }); + + var tr = new Transform(); + tr.width = 512; + tr.height = 512; + sourceCache.updateCacheSize(tr); + + sourceCache.addTile(coord); + sourceCache.removeTile(coord.id); + + t.end(); + }); + + t.test('aborts and unloads unfinished tile', function(t) { + var coord = new TileCoord(0, 0, 0), + abort = 0, + unload = 0; + + var sourceCache = createSourceCache({ + abortTile: function(tile) { + t.deepEqual(tile.coord, coord); + abort++; + }, + unloadTile: function(tile) { + t.deepEqual(tile.coord, coord); + unload++; + } + }); + + sourceCache.addTile(coord); + sourceCache.removeTile(coord.id); + + t.equal(abort, 1); + t.equal(unload, 1); + + t.end(); + }); + + t.end(); +}); + +test('SourceCache / Source lifecycle', function (t) { + t.test('does not fire load or change before source load event', function (t) { + createSourceCache({noLoad: true}) + .on('load', t.fail) + .on('change', t.fail); + setTimeout(t.end, 1); + }); + + t.test('forward load event', function (t) { + createSourceCache({}).on('load', t.end); + }); + + t.test('forward change event', function (t) { + var sourceCache = createSourceCache().on('change', t.end); + sourceCache.getSource().fire('change'); + }); + + t.test('forward error event', function (t) { + createSourceCache({ error: 'Error loading source' }) + .on('error', function (err) { + t.equal(err.error, 'Error loading source'); + t.end(); + }); + }); + + t.test('loaded() true after error', function (t) { + var sourceCache = createSourceCache({ error: 'Error loading source' }) + .on('error', function () { + t.ok(sourceCache.loaded()); + t.end(); + }); + }); + + t.test('reloads tiles after source change event', function (t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 0; + + var expected = [ new TileCoord(0, 0, 0).id, new TileCoord(0, 0, 0).id ]; + t.plan(expected.length); + + var sourceCache = createSourceCache({ + loadTile: function (tile, callback) { + t.equal(tile.coord.id, expected.shift()); + tile.loaded = true; + callback(); + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform); + sourceCache.getSource().fire('change'); + }); + }); + + t.end(); +}); + +test('SourceCache#update', function(t) { + t.test('loads no tiles if used is false', function(t) { + var transform = new Transform(); + transform.resize(512, 512); + transform.zoom = 0; + + var sourceCache = createSourceCache({}, false); + sourceCache.on('load', function () { + sourceCache.update(transform); + + t.deepEqual(sourceCache.getIds(), []); + t.end(); + }); + }); + + t.test('loads covering tiles', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 0; + + var sourceCache = createSourceCache({}); + sourceCache.on('load', function () { + sourceCache.update(transform); + t.deepEqual(sourceCache.getIds(), [new TileCoord(0, 0, 0).id]); + t.end(); + }); + }); + + t.test('removes unused tiles', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 0; + + var sourceCache = createSourceCache({ + load: function(tile) { + tile.state = 'loaded'; + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform); + t.deepEqual(sourceCache.getIds(), [new TileCoord(0, 0, 0).id]); + + transform.zoom = 1; + sourceCache.update(transform); + + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(1, 0, 0).id, + new TileCoord(1, 1, 0).id, + new TileCoord(1, 0, 1).id, + new TileCoord(1, 1, 1).id + ]); + t.end(); + }); + }); + + + t.test('retains parent tiles for pending children', function(t) { + var transform = new Transform(); + transform._test = 'retains'; + transform.resize(511, 511); + transform.zoom = 0; + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.state = (tile.coord.id === new TileCoord(0, 0, 0).id) ? 'loaded' : 'loading'; + callback(); + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform); + t.deepEqual(sourceCache.getIds(), [new TileCoord(0, 0, 0).id]); + + transform.zoom = 1; + sourceCache.update(transform); + + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(0, 0, 0).id, + new TileCoord(1, 0, 0).id, + new TileCoord(1, 1, 0).id, + new TileCoord(1, 0, 1).id, + new TileCoord(1, 1, 1).id + ]); + t.end(); + }); + }); + + t.test('retains parent tiles for pending children (wrapped)', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 0; + transform.center = new LngLat(360, 0); + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.state = (tile.coord.id === new TileCoord(0, 0, 0).id) ? 'loaded' : 'loading'; + callback(); + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform); + t.deepEqual(sourceCache.getIds(), [new TileCoord(0, 0, 0, 1).id]); + + transform.zoom = 1; + sourceCache.update(transform); + + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(0, 0, 0, 1).id, + new TileCoord(1, 0, 0, 1).id, + new TileCoord(1, 1, 0, 1).id, + new TileCoord(1, 0, 1, 1).id, + new TileCoord(1, 1, 1, 1).id + ]); + t.end(); + }); + }); + + t.test('includes partially covered tiles in rendered tiles', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 2; + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.timeAdded = Infinity; + tile.state = 'loaded'; + callback(); + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform, 100); + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(2, 1, 1).id, + new TileCoord(2, 2, 1).id, + new TileCoord(2, 1, 2).id, + new TileCoord(2, 2, 2).id + ]); + + transform.zoom = 0; + sourceCache.update(transform, 100); + + t.deepEqual(sourceCache.getRenderableIds().length, 5); + t.end(); + }); + }); + + t.test('retains a parent tile for fading even if a tile is partially covered by children', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 0; + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.timeAdded = Infinity; + tile.state = 'loaded'; + callback(); + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform, 100); + + transform.zoom = 2; + sourceCache.update(transform, 100); + + transform.zoom = 1; + sourceCache.update(transform, 100); + + t.equal(sourceCache._coveredTiles[(new TileCoord(0, 0, 0).id)], true); + t.end(); + }); + }); + + + t.test('retains overscaled loaded children', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 16; + transform.center = new LngLat(0, 0); + + + var sourceCache = createSourceCache({ + reparseOverscaled: true, + loadTile: function(tile, callback) { + tile.state = tile.coord.z === 16 ? 'loaded' : 'loading'; + callback(); + } + }); + + sourceCache.on('load', function () { + t.equal(sourceCache.maxzoom, 14); + + sourceCache.update(transform); + t.deepEqual(sourceCache.getRenderableIds(), [ + new TileCoord(16, 8191, 8191, 0).id, + new TileCoord(16, 8192, 8191, 0).id, + new TileCoord(16, 8192, 8192, 0).id, + new TileCoord(16, 8191, 8192, 0).id + ]); + + transform.zoom = 15; + sourceCache.update(transform); + + t.deepEqual(sourceCache.getRenderableIds(), [ + new TileCoord(16, 8191, 8191, 0).id, + new TileCoord(16, 8192, 8191, 0).id, + new TileCoord(16, 8192, 8192, 0).id, + new TileCoord(16, 8191, 8192, 0).id + ]); + t.end(); + }); + }); + + t.end(); +}); + +test('SourceCache#clearTiles', function(t) { + t.test('unloads tiles', function(t) { + var coord = new TileCoord(0, 0, 0), + abort = 0, + unload = 0; + + var sourceCache = createSourceCache({ + abortTile: function(tile) { + t.deepEqual(tile.coord, coord); + abort++; + }, + unloadTile: function(tile) { + t.deepEqual(tile.coord, coord); + unload++; + } + }); + + sourceCache.addTile(coord); + sourceCache.clearTiles(); + + t.equal(abort, 1); + t.equal(unload, 1); + + t.end(); + }); + + t.end(); +}); + +test('SourceCache#tilesIn', function (t) { + t.test('graceful response before source loaded', function (t) { + var sourceCache = createSourceCache({ noLoad: true }); + t.same(sourceCache.tilesIn([ + new Coordinate(0.5, 0.25, 1), + new Coordinate(1.5, 0.75, 1) + ]), []); + + t.end(); + }); + + t.test('regular tiles', function(t) { + var transform = new Transform(); + transform.resize(511, 511); + transform.zoom = 1; + + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.state = 'loaded'; + callback(); + } + }); + + sourceCache.on('load', function () { + sourceCache.update(transform); + + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(1, 0, 0).id, + new TileCoord(1, 1, 0).id, + new TileCoord(1, 0, 1).id, + new TileCoord(1, 1, 1).id + ]); + + var tiles = sourceCache.tilesIn([ + new Coordinate(0.5, 0.25, 1), + new Coordinate(1.5, 0.75, 1) + ]); + + tiles.sort(function (a, b) { return a.tile.coord.x - b.tile.coord.x; }); + tiles.forEach(function (result) { delete result.tile.uid; }); + + t.equal(tiles[0].tile.coord.id, 1); + t.equal(tiles[0].tile.tileSize, 512); + t.equal(tiles[0].scale, 1); + t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); + + t.equal(tiles[1].tile.coord.id, 33); + t.equal(tiles[1].tile.tileSize, 512); + t.equal(tiles[1].scale, 1); + t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); + + t.end(); + }); + }); + + t.test('reparsed overscaled tiles', function(t) { + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { tile.state = 'loaded'; callback(); }, + reparseOverscaled: true, + minzoom: 1, + maxzoom: 1, + tileSize: 512 + }); + + sourceCache.on('load', function () { + var transform = new Transform(); + transform.resize(512, 512); + transform.zoom = 2.0; + sourceCache.update(transform); + + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(2, 0, 0).id, + new TileCoord(2, 1, 0).id, + new TileCoord(2, 0, 1).id, + new TileCoord(2, 1, 1).id + ]); + + var tiles = sourceCache.tilesIn([ + new Coordinate(0.5, 0.25, 1), + new Coordinate(1.5, 0.75, 1) + ]); + + tiles.sort(function (a, b) { return a.tile.coord.x - b.tile.coord.x; }); + tiles.forEach(function (result) { delete result.tile.uid; }); + + t.equal(tiles[0].tile.coord.id, 2); + t.equal(tiles[0].tile.tileSize, 1024); + t.equal(tiles[0].scale, 1); + t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); + + t.equal(tiles[1].tile.coord.id, 34); + t.equal(tiles[1].tile.tileSize, 1024); + t.equal(tiles[1].scale, 1); + t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); + + t.end(); + }); + }); + + t.test('overscaled tiles', function(t) { + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { tile.state = 'loaded'; callback(); }, + reparseOverscaled: false, + minzoom: 1, + maxzoom: 1, + tileSize: 512 + }); + + sourceCache.on('load', function () { + var transform = new Transform(); + transform.resize(512, 512); + transform.zoom = 2.0; + sourceCache.update(transform); + + + t.end(); + }); + }); + + t.end(); +}); + +test('SourceCache#loaded (no errors)', function (t) { + var sourceCache = createSourceCache({ + loadTile: function(tile, callback) { + tile.state = 'loaded'; + callback(); + } + }); + + sourceCache.on('load', function () { + var coord = new TileCoord(0, 0, 0); + sourceCache.addTile(coord); + + t.ok(sourceCache.loaded()); + t.end(); + }); +}); + +test('SourceCache#loaded (with errors)', function (t) { + var sourceCache = createSourceCache({ + loadTile: function(tile) { + tile.state = 'errored'; + } + }); + + sourceCache.on('load', function () { + var coord = new TileCoord(0, 0, 0); + sourceCache.addTile(coord); + + t.ok(sourceCache.loaded()); + t.end(); + }); +}); + +test('SourceCache#getIds (ascending order by zoom level)', function(t) { + var ids = [ + new TileCoord(0, 0, 0), + new TileCoord(3, 0, 0), + new TileCoord(1, 0, 0), + new TileCoord(2, 0, 0) + ]; + + var sourceCache = createSourceCache({}); + for (var i = 0; i < ids.length; i++) { + sourceCache._tiles[ids[i].id] = {}; + } + t.deepEqual(sourceCache.getIds(), [ + new TileCoord(0, 0, 0).id, + new TileCoord(1, 0, 0).id, + new TileCoord(2, 0, 0).id, + new TileCoord(3, 0, 0).id + ]); + t.end(); +}); + + +test('SourceCache#findLoadedParent', function(t) { + + t.test('adds from previously used tiles (sourceCache._tiles)', function(t) { + var sourceCache = createSourceCache({}); + var tr = new Transform(); + tr.width = 512; + tr.height = 512; + sourceCache.updateCacheSize(tr); + + var tile = { + coord: new TileCoord(1, 0, 0), + isRenderable: function() { return true; } + }; + + sourceCache._tiles[tile.coord.id] = tile; + + var retain = {}; + var expectedRetain = {}; + expectedRetain[tile.coord.id] = true; + + t.equal(sourceCache.findLoadedParent(new TileCoord(2, 3, 3), 0, retain), undefined); + t.deepEqual(sourceCache.findLoadedParent(new TileCoord(2, 0, 0), 0, retain), tile); + t.deepEqual(retain, expectedRetain); + t.end(); + }); + + t.test('adds from cache', function(t) { + var sourceCache = createSourceCache({}); + var tr = new Transform(); + tr.width = 512; + tr.height = 512; + sourceCache.updateCacheSize(tr); + + var tile = { + coord: new TileCoord(1, 0, 0), + loaded: true + }; + + sourceCache._cache.add(tile.coord.id, tile); + + var retain = {}; + var expectedRetain = {}; + expectedRetain[tile.coord.id] = true; + + t.equal(sourceCache.findLoadedParent(new TileCoord(2, 3, 3), 0, retain), undefined); + t.equal(sourceCache.findLoadedParent(new TileCoord(2, 0, 0), 0, retain), tile); + t.deepEqual(retain, expectedRetain); + t.equal(sourceCache._cache.order.length, 0); + t.equal(sourceCache._tiles[tile.coord.id], tile); + + t.end(); + }); + + t.end(); +}); + +test('SourceCache#reload', function(t) { + t.test('before loaded', function(t) { + var sourceCache = createSourceCache({ noLoad: true }); + + t.doesNotThrow(function() { + sourceCache.reload(); + }, null, 'reload ignored gracefully'); + + t.end(); + }); + + t.end(); +}); diff --git a/test/js/source/tile_pyramid.test.js b/test/js/source/tile_pyramid.test.js deleted file mode 100644 index 9cdea325dbe..00000000000 --- a/test/js/source/tile_pyramid.test.js +++ /dev/null @@ -1,714 +0,0 @@ -'use strict'; - -var test = require('tap').test; -var TilePyramid = require('../../../js/source/tile_pyramid'); -var TileCoord = require('../../../js/source/tile_coord'); -var Transform = require('../../../js/geo/transform'); -var LngLat = require('../../../js/geo/lng_lat'); -var Coordinate = require('../../../js/geo/coordinate'); -var util = require('../../../js/util/util'); - -test('TilePyramid#coveringTiles', function(t) { - var pyramid = new TilePyramid({ - minzoom: 1, - maxzoom: 10, - tileSize: 512 - }); - - var transform = new Transform(); - transform.resize(200, 200); - - transform.zoom = 0; - t.deepEqual(pyramid.coveringTiles(transform), []); - - transform.zoom = 1; - t.deepEqual(pyramid.coveringTiles(transform), ['1', '33', '65', '97'].map(TileCoord.fromID)); - - transform.zoom = 2.4; - t.deepEqual(pyramid.coveringTiles(transform), ['162', '194', '290', '322'].map(TileCoord.fromID)); - - transform.zoom = 10; - t.deepEqual(pyramid.coveringTiles(transform), ['16760810', '16760842', '16793578', '16793610'].map(TileCoord.fromID)); - - transform.zoom = 11; - t.deepEqual(pyramid.coveringTiles(transform), ['16760810', '16760842', '16793578', '16793610'].map(TileCoord.fromID)); - - t.end(); -}); - -test('TilePyramid#coveringZoomLevel', function(t) { - var pyramid = new TilePyramid({ - minzoom: 1, - maxzoom: 10, - tileSize: 512 - }); - - var transform = new Transform(); - - transform.zoom = 0; - t.deepEqual(pyramid.coveringZoomLevel(transform), 0); - - transform.zoom = 0.1; - t.deepEqual(pyramid.coveringZoomLevel(transform), 0); - - transform.zoom = 1; - t.deepEqual(pyramid.coveringZoomLevel(transform), 1); - - transform.zoom = 2.4; - t.deepEqual(pyramid.coveringZoomLevel(transform), 2); - - transform.zoom = 10; - t.deepEqual(pyramid.coveringZoomLevel(transform), 10); - - transform.zoom = 11; - t.deepEqual(pyramid.coveringZoomLevel(transform), 11); - - transform.zoom = 11.5; - t.deepEqual(pyramid.coveringZoomLevel(transform), 11); - - pyramid.tileSize = 256; - - transform.zoom = 0; - t.deepEqual(pyramid.coveringZoomLevel(transform), 1); - - transform.zoom = 0.1; - t.deepEqual(pyramid.coveringZoomLevel(transform), 1); - - transform.zoom = 1; - t.deepEqual(pyramid.coveringZoomLevel(transform), 2); - - transform.zoom = 2.4; - t.deepEqual(pyramid.coveringZoomLevel(transform), 3); - - transform.zoom = 10; - t.deepEqual(pyramid.coveringZoomLevel(transform), 11); - - transform.zoom = 11; - t.deepEqual(pyramid.coveringZoomLevel(transform), 12); - - transform.zoom = 11.5; - t.deepEqual(pyramid.coveringZoomLevel(transform), 12); - - pyramid.roundZoom = true; - - t.deepEqual(pyramid.coveringZoomLevel(transform), 13); - - t.end(); -}); - -function createPyramid(options) { - return new TilePyramid(util.extend({ - tileSize: 512, - minzoom: 0, - maxzoom: 14, - load: function() {}, - abort: function() {}, - unload: function() {}, - add: function() {}, - remove: function() {} - }, options)); -} - -test('TilePyramid#addTile', function(t) { - t.test('loads tile when uncached', function(t) { - var coord = new TileCoord(0, 0, 0); - var pyramid = createPyramid({ - load: function(tile) { - t.deepEqual(tile.coord, coord); - t.equal(tile.uses, 0); - t.end(); - } - }); - pyramid.addTile(coord); - }); - - t.test('adds tile when uncached', function(t) { - var coord = new TileCoord(0, 0, 0); - var pyramid = createPyramid({ - add: function(tile) { - t.deepEqual(tile.coord, coord); - t.equal(tile.uses, 1); - t.end(); - } - }); - pyramid.addTile(coord); - }); - - t.test('uses cached tile', function(t) { - var coord = new TileCoord(0, 0, 0), - load = 0, - add = 0; - - var pyramid = createPyramid({ - load: function(tile) { tile.state = 'loaded'; load++; }, - add: function() { add++; } - }); - - var tr = new Transform(); - tr.width = 512; - tr.height = 512; - pyramid.updateCacheSize(tr); - pyramid.addTile(coord); - pyramid.removeTile(coord.id); - pyramid.addTile(coord); - - t.equal(load, 1); - t.equal(add, 2); - - t.end(); - }); - - t.test('reuses wrapped tile', function(t) { - var coord = new TileCoord(0, 0, 0), - load = 0, - add = 0; - - var pyramid = createPyramid({ - load: function(tile) { tile.state = 'loaded'; load++; }, - add: function() { add++; } - }); - - var t1 = pyramid.addTile(coord); - var t2 = pyramid.addTile(new TileCoord(0, 0, 0, 1)); - - t.equal(load, 1); - t.equal(add, 2); - t.equal(t1, t2); - - t.end(); - }); - - t.end(); -}); - -test('TilePyramid#removeTile', function(t) { - t.test('removes tile', function(t) { - var coord = new TileCoord(0, 0, 0); - var pyramid = createPyramid({ - remove: function(tile) { - t.deepEqual(tile.coord, coord); - t.equal(tile.uses, 0); - t.end(); - } - }); - pyramid.addTile(coord); - pyramid.removeTile(coord.id); - }); - - t.test('caches (does not unload) loaded tile', function(t) { - var coord = new TileCoord(0, 0, 0); - var pyramid = createPyramid({ - load: function(tile) { - tile.state = 'loaded'; - }, - unload: function() { - t.fail(); - } - }); - - var tr = new Transform(); - tr.width = 512; - tr.height = 512; - pyramid.updateCacheSize(tr); - - pyramid.addTile(coord); - pyramid.removeTile(coord.id); - - t.end(); - }); - - t.test('aborts and unloads unfinished tile', function(t) { - var coord = new TileCoord(0, 0, 0), - abort = 0, - unload = 0; - - var pyramid = createPyramid({ - abort: function(tile) { - t.deepEqual(tile.coord, coord); - abort++; - }, - unload: function(tile) { - t.deepEqual(tile.coord, coord); - unload++; - } - }); - - pyramid.addTile(coord); - pyramid.removeTile(coord.id); - - t.equal(abort, 1); - t.equal(unload, 1); - - t.end(); - }); - - t.end(); -}); - -test('TilePyramid#update', function(t) { - t.test('loads no tiles if used is false', function(t) { - var transform = new Transform(); - transform.resize(512, 512); - transform.zoom = 0; - - var pyramid = createPyramid({}); - pyramid.update(false, transform); - - t.deepEqual(pyramid.getIds(), []); - t.end(); - }); - - t.test('loads covering tiles', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 0; - - var pyramid = createPyramid({}); - pyramid.update(true, transform); - - t.deepEqual(pyramid.getIds(), [new TileCoord(0, 0, 0).id]); - t.end(); - }); - - t.test('removes unused tiles', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 0; - - var pyramid = createPyramid({ - load: function(tile) { - tile.state = 'loaded'; - } - }); - - pyramid.update(true, transform); - t.deepEqual(pyramid.getIds(), [new TileCoord(0, 0, 0).id]); - - transform.zoom = 1; - pyramid.update(true, transform); - - t.deepEqual(pyramid.getIds(), [ - new TileCoord(1, 0, 0).id, - new TileCoord(1, 1, 0).id, - new TileCoord(1, 0, 1).id, - new TileCoord(1, 1, 1).id - ]); - t.end(); - }); - - t.test('retains parent tiles for pending children', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 0; - - var pyramid = createPyramid({ - load: function(tile) { - if (tile.coord.id === new TileCoord(0, 0, 0).id) { - tile.state = 'loaded'; - } - } - }); - - pyramid.update(true, transform); - t.deepEqual(pyramid.getIds(), [new TileCoord(0, 0, 0).id]); - - transform.zoom = 1; - pyramid.update(true, transform); - - t.deepEqual(pyramid.getIds(), [ - new TileCoord(0, 0, 0).id, - new TileCoord(1, 0, 0).id, - new TileCoord(1, 1, 0).id, - new TileCoord(1, 0, 1).id, - new TileCoord(1, 1, 1).id - ]); - t.end(); - }); - - t.test('retains parent tiles for pending children (wrapped)', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 0; - transform.center = new LngLat(360, 0); - - var pyramid = createPyramid({ - load: function(tile) { - if (tile.coord.id === new TileCoord(0, 0, 0).id) { - tile.state = 'loaded'; - } - } - }); - - pyramid.update(true, transform); - t.deepEqual(pyramid.getIds(), [new TileCoord(0, 0, 0, 1).id]); - - transform.zoom = 1; - pyramid.update(true, transform); - - t.deepEqual(pyramid.getIds(), [ - new TileCoord(0, 0, 0, 1).id, - new TileCoord(1, 0, 0, 1).id, - new TileCoord(1, 1, 0, 1).id, - new TileCoord(1, 0, 1, 1).id, - new TileCoord(1, 1, 1, 1).id - ]); - t.end(); - }); - - t.test('includes partially covered tiles in rendered tiles', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 2; - - var pyramid = createPyramid({ - load: function(tile) { - tile.timeAdded = Infinity; - tile.state = 'loaded'; - } - }); - - pyramid.update(true, transform, 100); - t.deepEqual(pyramid.getIds(), [ - new TileCoord(2, 1, 1).id, - new TileCoord(2, 2, 1).id, - new TileCoord(2, 1, 2).id, - new TileCoord(2, 2, 2).id - ]); - - transform.zoom = 0; - pyramid.update(true, transform, 100); - - t.deepEqual(pyramid.getRenderableIds().length, 5); - t.end(); - }); - - t.test('retains a parent tile for fading even if a tile is partially covered by children', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 0; - - var pyramid = createPyramid({ - load: function(tile) { - tile.timeAdded = Infinity; - tile.state = 'loaded'; - } - }); - - pyramid.update(true, transform, 100); - - transform.zoom = 2; - pyramid.update(true, transform, 100); - - transform.zoom = 1; - pyramid.update(true, transform, 100); - - t.equal(pyramid._coveredTiles[(new TileCoord(0, 0, 0).id)], true); - t.end(); - }); - - - t.test('retains overscaled loaded children', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 16; - transform.center = new LngLat(0, 0); - - var pyramid = createPyramid({ - reparseOverscaled: true, - load: function(tile) { - if (tile.coord.z === 16) { - tile.state = 'loaded'; - } - } - }); - - t.equal(pyramid.maxzoom, 14); - - pyramid.update(true, transform); - t.deepEqual(pyramid.getRenderableIds(), [ - new TileCoord(16, 8191, 8191, 0).id, - new TileCoord(16, 8192, 8191, 0).id, - new TileCoord(16, 8192, 8192, 0).id, - new TileCoord(16, 8191, 8192, 0).id - ]); - - transform.zoom = 15; - pyramid.update(true, transform); - - t.deepEqual(pyramid.getRenderableIds(), [ - new TileCoord(16, 8191, 8191, 0).id, - new TileCoord(16, 8192, 8191, 0).id, - new TileCoord(16, 8192, 8192, 0).id, - new TileCoord(16, 8191, 8192, 0).id - ]); - t.end(); - - }); - - t.end(); -}); - -test('TilePyramid#clearTiles', function(t) { - t.test('unloads tiles', function(t) { - var coord = new TileCoord(0, 0, 0), - abort = 0, - unload = 0; - - var pyramid = createPyramid({ - abort: function(tile) { - t.deepEqual(tile.coord, coord); - abort++; - }, - unload: function(tile) { - t.deepEqual(tile.coord, coord); - unload++; - } - }); - - pyramid.addTile(coord); - pyramid.clearTiles(); - - t.equal(abort, 1); - t.equal(unload, 1); - - t.end(); - }); - - t.end(); -}); - -test('TilePyramid#tilesIn', function (t) { - t.test('regular tiles', function(t) { - var transform = new Transform(); - transform.resize(511, 511); - transform.zoom = 1; - - var pyramid = createPyramid({ - load: function(tile) { - tile.state = 'loaded'; - } - }); - - pyramid.update(true, transform); - - t.deepEqual(pyramid.getIds(), [ - new TileCoord(1, 0, 0).id, - new TileCoord(1, 1, 0).id, - new TileCoord(1, 0, 1).id, - new TileCoord(1, 1, 1).id - ]); - - var tiles = pyramid.tilesIn([ - new Coordinate(0.5, 0.25, 1), - new Coordinate(1.5, 0.75, 1) - ]); - - tiles.sort(function (a, b) { return a.tile.coord.x - b.tile.coord.x; }); - tiles.forEach(function (result) { delete result.tile.uid; }); - - t.equal(tiles[0].tile.coord.id, 1); - t.equal(tiles[0].tile.tileSize, 512); - t.equal(tiles[0].scale, 1); - t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); - - t.equal(tiles[1].tile.coord.id, 33); - t.equal(tiles[1].tile.tileSize, 512); - t.equal(tiles[1].scale, 1); - t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); - - t.end(); - }); - - t.test('reparsed overscaled tiles', function(t) { - var pyramid = createPyramid({ - load: function(tile) { tile.state = 'loaded'; }, - reparseOverscaled: true, - minzoom: 1, - maxzoom: 1, - tileSize: 512 - }); - - var transform = new Transform(); - transform.resize(512, 512); - transform.zoom = 2.0; - pyramid.update(true, transform); - - t.deepEqual(pyramid.getIds(), [ - new TileCoord(2, 0, 0).id, - new TileCoord(2, 1, 0).id, - new TileCoord(2, 0, 1).id, - new TileCoord(2, 1, 1).id - ]); - - var tiles = pyramid.tilesIn([ - new Coordinate(0.5, 0.25, 1), - new Coordinate(1.5, 0.75, 1) - ]); - - tiles.sort(function (a, b) { return a.tile.coord.x - b.tile.coord.x; }); - tiles.forEach(function (result) { delete result.tile.uid; }); - - t.equal(tiles[0].tile.coord.id, 2); - t.equal(tiles[0].tile.tileSize, 1024); - t.equal(tiles[0].scale, 1); - t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); - - t.equal(tiles[1].tile.coord.id, 34); - t.equal(tiles[1].tile.tileSize, 1024); - t.equal(tiles[1].scale, 1); - t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); - - t.end(); - }); - - t.test('overscaled tiles', function(t) { - var pyramid = createPyramid({ - load: function(tile) { tile.state = 'loaded'; }, - reparseOverscaled: false, - minzoom: 1, - maxzoom: 1, - tileSize: 512 - }); - - var transform = new Transform(); - transform.resize(512, 512); - transform.zoom = 2.0; - pyramid.update(true, transform); - - - t.deepEqual(pyramid.getIds(), [ - new TileCoord(1, 0, 0).id, - new TileCoord(1, 1, 0).id, - new TileCoord(1, 0, 1).id, - new TileCoord(1, 1, 1).id - ]); - - var tiles = pyramid.tilesIn([ - new Coordinate(0.5, 0.25, 1), - new Coordinate(1.5, 0.75, 1) - ]); - - tiles.sort(function (a, b) { return a.tile.coord.x - b.tile.coord.x; }); - tiles.forEach(function (result) { delete result.tile.uid; }); - - t.equal(tiles[0].tile.coord.id, 1); - t.equal(tiles[0].tile.tileSize, 512); - t.equal(tiles[0].scale, 2); - t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); - - t.equal(tiles[1].tile.coord.id, 33); - t.equal(tiles[1].tile.tileSize, 512); - t.equal(tiles[1].scale, 2); - t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); - - t.end(); - }); - - t.end(); -}); - -test('TilePyramid#loaded (no errors)', function (t) { - var pyramid = createPyramid({ - load: function(tile) { - tile.state = 'loaded'; - } - }); - - var coord = new TileCoord(0, 0, 0); - pyramid.addTile(coord); - - t.ok(pyramid.loaded()); - t.end(); -}); - -test('TilePyramid#loaded (with errors)', function (t) { - var pyramid = createPyramid({ - load: function(tile) { - tile.state = 'errored'; - } - }); - - var coord = new TileCoord(0, 0, 0); - pyramid.addTile(coord); - - t.ok(pyramid.loaded()); - t.end(); -}); - -test('TilePyramid#getIds (ascending order by zoom level)', function(t) { - var ids = [ - new TileCoord(0, 0, 0), - new TileCoord(3, 0, 0), - new TileCoord(1, 0, 0), - new TileCoord(2, 0, 0) - ]; - - var pyramid = createPyramid({}); - for (var i = 0; i < ids.length; i++) { - pyramid._tiles[ids[i].id] = {}; - } - t.deepEqual(pyramid.getIds(), [ - new TileCoord(0, 0, 0).id, - new TileCoord(1, 0, 0).id, - new TileCoord(2, 0, 0).id, - new TileCoord(3, 0, 0).id - ]); - t.end(); -}); - - -test('TilePyramid#findLoadedParent', function(t) { - - t.test('adds from previously used tiles (pyramid._tiles)', function(t) { - var pyramid = createPyramid({}); - var tr = new Transform(); - tr.width = 512; - tr.height = 512; - pyramid.updateCacheSize(tr); - - var tile = { - coord: new TileCoord(1, 0, 0), - isRenderable: function() { return true; } - }; - - pyramid._tiles[tile.coord.id] = tile; - - var retain = {}; - var expectedRetain = {}; - expectedRetain[tile.coord.id] = true; - - t.equal(pyramid.findLoadedParent(new TileCoord(2, 3, 3), 0, retain), undefined); - t.deepEqual(pyramid.findLoadedParent(new TileCoord(2, 0, 0), 0, retain), tile); - t.deepEqual(retain, expectedRetain); - t.end(); - }); - - t.test('adds from cache', function(t) { - var pyramid = createPyramid({}); - var tr = new Transform(); - tr.width = 512; - tr.height = 512; - pyramid.updateCacheSize(tr); - - var tile = { - coord: new TileCoord(1, 0, 0), - loaded: true - }; - - pyramid._cache.add(tile.coord.id, tile); - - var retain = {}; - var expectedRetain = {}; - expectedRetain[tile.coord.id] = true; - - t.equal(pyramid.findLoadedParent(new TileCoord(2, 3, 3), 0, retain), undefined); - t.equal(pyramid.findLoadedParent(new TileCoord(2, 0, 0), 0, retain), tile); - t.deepEqual(retain, expectedRetain); - t.equal(pyramid._cache.order.length, 0); - t.equal(pyramid._tiles[tile.coord.id], tile); - - t.end(); - }); - - t.end(); -}); diff --git a/test/js/source/vector_tile_source.test.js b/test/js/source/vector_tile_source.test.js index a1a646223d2..f48364154aa 100644 --- a/test/js/source/vector_tile_source.test.js +++ b/test/js/source/vector_tile_source.test.js @@ -10,14 +10,12 @@ var TileCoord = require('../../../js/source/tile_coord'); var server = http.createServer(st({path: path.join(__dirname, '/../../fixtures')})); function createSource(options) { - var source = new VectorTileSource(options); + var source = new VectorTileSource('id', options, { send: function() {} }); source.on('error', function(e) { throw e.error; }); - source.dispatcher = { send: function() {} }; - source.map = { transform: { angle: 0, pitch: 0, showCollisionBoxes: false } }; @@ -39,7 +37,6 @@ test('VectorTileSource', function(t) { }); source.on('load', function() { - t.ok(source.loaded()); t.deepEqual(source.tiles, ["http://example.com/{z}/{x}/{y}.png"]); t.deepEqual(source.minzoom, 1); t.deepEqual(source.maxzoom, 10); @@ -54,7 +51,6 @@ test('VectorTileSource', function(t) { }); source.on('load', function() { - t.ok(source.loaded()); t.deepEqual(source.tiles, ["http://example.com/{z}/{x}/{y}.png"]); t.deepEqual(source.minzoom, 1); t.deepEqual(source.maxzoom, 10); @@ -63,20 +59,6 @@ test('VectorTileSource', function(t) { }); }); - t.test('ignores reload before loaded', function(t) { - var source = createSource({ - url: "http://localhost:2900/source.json" - }); - - t.doesNotThrow(function() { - source.reload(); - }, null, 'reload ignored gracefully'); - - source.on('load', function() { - t.end(); - }); - }); - t.test('serialize URL', function(t) { var source = createSource({ url: "http://localhost:2900/source.json" @@ -105,20 +87,6 @@ test('VectorTileSource', function(t) { t.end(); }); - t.test('queryRenderedFeatures', function(t) { - t.test('returns an empty object before loaded', function(t) { - var source = createSource({ - minzoom: 1, - maxzoom: 10, - attribution: "Mapbox", - tiles: ["http://example.com/{z}/{x}/{y}.png"] - }); - t.deepEqual(source.queryRenderedFeatures(), []); - t.end(); - }); - t.end(); - }); - function testScheme(scheme, expectedURL) { t.test('scheme "' + scheme + '"', function(t) { var source = createSource({ @@ -136,7 +104,7 @@ test('VectorTileSource', function(t) { }; source.on('load', function() { - source._loadTile({coord: new TileCoord(10, 5, 5, 0)}); + source.loadTile({coord: new TileCoord(10, 5, 5, 0)}, function () {}); }); }); } diff --git a/test/js/source/vector_tile_worker_source.test.js b/test/js/source/vector_tile_worker_source.test.js new file mode 100644 index 00000000000..fbc566c8651 --- /dev/null +++ b/test/js/source/vector_tile_worker_source.test.js @@ -0,0 +1,54 @@ +'use strict'; + +var test = require('tap').test; +var VectorTileWorkerSource = require('../../../js/source/vector_tile_worker_source'); + +var styleLayers = { + getLayers: function () {}, + getLayerFamilies: function () {} +}; + +test('VectorWorkerTile#abortTile', function(t) { + t.test('aborts pending request', function(t) { + var vectorWorkerSource = new VectorTileWorkerSource(null, styleLayers); + + vectorWorkerSource.loadTile({ + source: 'source', + uid: 0, + url: 'http://localhost:2900/abort' + }, t.fail); + + vectorWorkerSource.abortTile({ + source: 'source', + uid: 0 + }); + + t.deepEqual(vectorWorkerSource.loading, { source: {} }); + t.end(); + }); + + t.end(); +}); + +test('remove tile', function(t) { + t.test('removes loaded tile', function(t) { + var vectorWorkerSource = new VectorTileWorkerSource(null, styleLayers); + + vectorWorkerSource.loaded = { + source: { + '0': {} + } + }; + + vectorWorkerSource.removeTile({ + source: 'source', + uid: 0 + }); + + t.deepEqual(vectorWorkerSource.loaded, { source: {} }); + t.end(); + }); + + t.end(); +}); + diff --git a/test/js/source/worker.test.js b/test/js/source/worker.test.js index 08ed305d5e8..dcea5af78f6 100644 --- a/test/js/source/worker.test.js +++ b/test/js/source/worker.test.js @@ -39,28 +39,6 @@ test('load tile', function(t) { t.end(); }); -test('abort tile', function(t) { - t.test('aborts pending request', function(t) { - var worker = new Worker(_self); - - worker['load tile']({ - source: 'source', - uid: 0, - url: 'http://localhost:2900/abort' - }, t.fail); - - worker['abort tile']({ - source: 'source', - uid: 0 - }); - - t.deepEqual(worker.loading, { source: {} }); - t.end(); - }); - - t.end(); -}); - test('set layers', function(t) { var worker = new Worker(_self); @@ -109,28 +87,6 @@ test('update layers', function(t) { t.end(); }); -test('remove tile', function(t) { - t.test('removes loaded tile', function(t) { - var worker = new Worker(_self); - - worker.loaded = { - source: { - '0': {} - } - }; - - worker['remove tile']({ - source: 'source', - uid: 0 - }); - - t.deepEqual(worker.loaded, { source: {} }); - t.end(); - }); - - t.end(); -}); - test('after', function(t) { server.close(t.end); }); diff --git a/test/js/style/style.test.js b/test/js/style/style.test.js index 2b2e9dfcedc..d36dfc47e20 100644 --- a/test/js/style/style.test.js +++ b/test/js/style/style.test.js @@ -5,8 +5,9 @@ var st = require('st'); var http = require('http'); var path = require('path'); var sinon = require('sinon'); +var proxyquire = require('proxyquire'); var Style = require('../../../js/style/style'); -var VectorTileSource = require('../../../js/source/vector_tile_source'); +var SourceCache = require('../../../js/source/source_cache'); var StyleLayer = require('../../../js/style/style_layer'); var util = require('../../../js/util/util'); @@ -19,13 +20,13 @@ function createStyleJSON(properties) { } function createSource() { - return new VectorTileSource({ + return { type: 'vector', minzoom: 1, maxzoom: 10, attribution: 'Mapbox', tiles: ['http://example.com/{z}/{x}/{y}.png'] - }); + }; } function createGeoJSONSource() { @@ -68,7 +69,7 @@ test('Style', function(t) { } })); style.on('load', function() { - t.ok(style.getSource('mapbox') instanceof VectorTileSource); + t.ok(style.sources['mapbox'] instanceof SourceCache); t.end(); }); }); @@ -90,9 +91,9 @@ test('Style', function(t) { style.removeSource('-source-id-'); var source = createSource(); + source['vector_layers'] = [{ id: 'green' }]; style.addSource('-source-id-', source); style.update(); - source.vectorLayerIds = ['green']; }); style.on('error', function(event) { @@ -273,11 +274,25 @@ test('Style#addSource', function(t) { }); }); + t.test('throw if missing source type', function(t) { + var style = new Style(createStyleJSON()), + source = createSource(); + + delete source.type; + + style.on('load', function() { + t.throws(function () { + style.addSource('source-id', source); + }, Error, /type/i); + t.end(); + }); + }); + t.test('fires source.add', function(t) { var style = new Style(createStyleJSON()), source = createSource(); style.on('source.add', function(e) { - t.equal(e.source, source); + t.same(e.source.serialize(), source); t.end(); }); style.on('load', function () { @@ -302,7 +317,7 @@ test('Style#addSource', function(t) { var style = new Style(createStyleJSON()); style.on('load', function() { style.on('error', function() { - t.notOk(style.getSource('source-id')); + t.notOk(style.sources['source-id']); t.end(); }); style.addSource('source-id', { @@ -325,11 +340,11 @@ test('Style#addSource', function(t) { var source = createSource(); function sourceEvent(e) { - t.equal(e.source, source); + t.same(e.source.serialize(), source); } function tileEvent(e) { - t.equal(e.source, source); + t.same(e.source.serialize(), source); } style.on('source.load', sourceEvent); @@ -343,12 +358,12 @@ test('Style#addSource', function(t) { style.on('load', function () { t.plan(7); style.addSource('source-id', source); // Fires load - source.fire('error'); - source.fire('change'); - source.fire('tile.add'); - source.fire('tile.load'); - source.fire('tile.error'); - source.fire('tile.remove'); + style.sources['source-id'].fire('error'); + style.sources['source-id'].fire('change'); + style.sources['source-id'].fire('tile.add'); + style.sources['source-id'].fire('tile.load'); + style.sources['source-id'].fire('tile.error'); + style.sources['source-id'].fire('tile.remove'); }); }); @@ -387,7 +402,7 @@ test('Style#removeSource', function(t) { var style = new Style(createStyleJSON()), source = createSource(); style.on('source.remove', function(e) { - t.equal(e.source, source); + t.same(e.source.serialize(), source); t.end(); }); style.on('load', function () { @@ -431,6 +446,8 @@ test('Style#removeSource', function(t) { style.on('load', function () { style.addSource('source-id', source); + source = style.sources['source-id']; + style.removeSource('source-id'); // Bind a listener to prevent fallback Evented error reporting. @@ -501,6 +518,7 @@ test('Style#addLayer', function(t) { style.on('load', function() { var source = createSource(); + source['vector_layers'] = [{id: 'green'}]; style.addSource('-source-id-', source); style.addLayer({ 'id': '-layer-id-', @@ -508,7 +526,6 @@ test('Style#addLayer', function(t) { 'source': '-source-id-', 'source-layer': '-source-layer-' }); - source.vectorLayerIds = ['green']; }); style.on('error', function(event) { @@ -558,7 +575,7 @@ test('Style#addLayer', function(t) { }; style.on('load', function() { - style.getSource('mapbox').reload = t.end; + style.sources['mapbox'].reload = t.end; style.addLayer(layer); style.update(); @@ -908,7 +925,51 @@ test('Style#setLayerZoomRange', function(t) { }); test('Style#queryRenderedFeatures', function(t) { - var style = new Style({ + var style; + var Style = proxyquire('../../../js/style/style', { + '../source/query_features': { + rendered: function(source, layers, queryGeom, params) { + if (source.id !== 'mapbox') { + return []; + } + + var features = { + 'land': [{ + type: 'Feature', + layer: style._layers.land, + geometry: { + type: 'Polygon' + } + }, { + type: 'Feature', + layer: style._layers.land, + geometry: { + type: 'Point' + } + }], + 'landref': [{ + type: 'Feature', + layer: style._layers.landref, + geometry: { + type: 'Line' + } + }] + }; + + if (params.layers) { + for (var l in features) { + if (params.layers.indexOf(l) < 0) { + delete features[l]; + } + } + } + + return features; + } + } + }); + + style = new Style({ "version": 8, "sources": { "mapbox": { @@ -961,53 +1022,6 @@ test('Style#queryRenderedFeatures', function(t) { style._applyClasses([]); style._recalculate(0); - style.sources.other.queryRenderedFeatures = function(position, params) { - var features = {'land--other': []}; - if (params.layers) { - for (var l in features) { - if (params.layers.indexOf(l) < 0) { - delete features[l]; - } - } - } - return features; - }; - - style.sources.mapbox.queryRenderedFeatures = function(position, params) { - var features = { - 'land': [{ - type: 'Feature', - layer: style._layers.land, - geometry: { - type: 'Polygon' - } - }, { - type: 'Feature', - layer: style._layers.land, - geometry: { - type: 'Point' - } - }], - 'landref': [{ - type: 'Feature', - layer: style._layers.landref, - geometry: { - type: 'Line' - } - }] - }; - - if (params.layers) { - for (var l in features) { - if (params.layers.indexOf(l) < 0) { - delete features[l]; - } - } - } - - return features; - }; - t.test('returns feature type', function(t) { var results = style.queryRenderedFeatures([{column: 1, row: 1, zoom: 1}], {}, 0, 0); t.equal(results[0].geometry.type, 'Line'); @@ -1165,6 +1179,60 @@ test('Style#query*Features', function(t) { t.end(); }); +test('Style#addSourceType', function (t) { + var _types = { 'existing': function () {} }; + var Style = proxyquire('../../../js/style/style', { + '../source/source': { + getType: function (name) { return _types[name]; }, + setType: function (name, create) { _types[name] = create; } + } + }); + + t.test('adds factory function', function (t) { + var style = new Style(createStyleJSON()); + var SourceType = function () {}; + + // expect no call to load worker source + style.dispatcher.broadcast = function (type) { + if (type === 'load worker source') { + t.fail(); + } + }; + + style.addSourceType('foo', SourceType, function () { + t.equal(_types['foo'], SourceType); + t.end(); + }); + }); + + t.test('triggers workers to load worker source code', function (t) { + var style = new Style(createStyleJSON()); + var SourceType = function () {}; + SourceType.workerSourceURL = 'worker-source.js'; + + style.dispatcher.broadcast = function (type, params) { + if (type === 'load worker source') { + t.equal(_types['bar'], SourceType); + t.equal(params.name, 'bar'); + t.equal(params.url, 'worker-source.js'); + t.end(); + } + }; + + style.addSourceType('bar', SourceType, function (err) { t.error(err); }); + }); + + t.test('refuses to add new type over existing name', function (t) { + var style = new Style(createStyleJSON()); + style.addSourceType('existing', function () {}, function (err) { + t.ok(err); + t.end(); + }); + }); + + t.end(); +}); + test('Style creates correct number of workers', function(t) { var style = new Style(createStyleJSON(), null, 3); t.equal(style.dispatcher.actors.length, 3);