From e2989269e0a36ed06e8fdeb42396ea13de810e47 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 31 Jul 2026 02:38:11 +0200 Subject: [PATCH 1/2] Stream OpenStreetMap data in geohash globe --- app/src/main/assets/world_borders.geojson | 1 - app/src/main/assets/world_cities.geojson | 1 - app/src/main/assets/world_land.geojson | 1 - .../android/ui/GeohashPickerActivity.kt | 193 ++- .../bitchat/android/ui/globe/GlobeMapData.kt | 197 +++ .../bitchat/android/ui/globe/GlobeState.kt | 20 +- .../android/ui/globe/GlobeTileSelector.kt | 232 +++ .../com/bitchat/android/ui/globe/GlobeView.kt | 1238 ++++++++++++----- .../com/bitchat/android/ui/globe/LandData.kt | 164 --- .../bitchat/android/ui/globe/MvtDecoder.kt | 614 ++++++++ .../ui/globe/StreamedGlobeRepository.kt | 264 ++++ app/src/main/res/values/strings.xml | 3 + .../android/ui/globe/GlobeGeometryTest.kt | 66 +- .../ui/globe/GlobeInteractionPolicyTest.kt | 86 +- .../android/ui/globe/GlobeTileSelectorTest.kt | 95 ++ .../android/ui/globe/MvtDecoderTest.kt | 237 ++++ 16 files changed, 2843 insertions(+), 569 deletions(-) delete mode 100644 app/src/main/assets/world_borders.geojson delete mode 100644 app/src/main/assets/world_cities.geojson delete mode 100644 app/src/main/assets/world_land.geojson create mode 100644 app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt create mode 100644 app/src/main/java/com/bitchat/android/ui/globe/GlobeTileSelector.kt delete mode 100644 app/src/main/java/com/bitchat/android/ui/globe/LandData.kt create mode 100644 app/src/main/java/com/bitchat/android/ui/globe/MvtDecoder.kt create mode 100644 app/src/main/java/com/bitchat/android/ui/globe/StreamedGlobeRepository.kt create mode 100644 app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeTileSelectorTest.kt create mode 100644 app/src/test/kotlin/com/bitchat/android/ui/globe/MvtDecoderTest.kt diff --git a/app/src/main/assets/world_borders.geojson b/app/src/main/assets/world_borders.geojson deleted file mode 100644 index a8cddd07..00000000 --- a/app/src/main/assets/world_borders.geojson +++ /dev/null @@ -1 +0,0 @@ -{"type":"GeometryCollection","geometries":[{"type":"LineString","coordinates":[[-130.54,54.8],[-129.98,55.28],[-130.01,55.92],[-131.71,56.55],[-132.73,57.69],[-133.36,58.41],[-134.27,58.86],[-134.94,59.27],[-135.48,59.79],[-136.48,59.46],[-137.45,58.91],[-138.34,59.56],[-139.04,60],[-140.01,60.28],[-141.0,60.31],[-140.99,66.0],[-140.99,69.71]]},{"type":"LineString","coordinates":[[-117.13,32.54],[-115.99,32.61],[-114.72,32.72],[-114.81,32.53],[-113.3,32.04],[-111.02,31.33],[-109.04,31.34],[-108.24,31.34],[-108.24,31.75],[-106.51,31.75],[-106.14,31.4],[-105.63,31.08],[-105.04,30.64],[-104.71,30.12],[-104.46,29.57],[-103.94,29.27],[-103.11,28.97],[-102.48,29.76],[-101.66,29.78],[-100.96,29.38],[-100.46,28.7],[-100.11,28.11],[-99.52,27.54],[-99.3,26.84],[-99.02,26.37],[-98.24,26.06],[-97.53,25.84],[-97.14,25.87]]},{"type":"LineString","coordinates":[[-90.1,13.74],[-90.06,13.88],[-89.72,14.13],[-89.53,14.24],[-89.59,14.36],[-89.35,14.42]]},{"type":"LineString","coordinates":[[-92.23,14.54],[-92.2,14.83],[-92.09,15.06],[-92.23,15.25],[-91.75,16.07],[-90.46,16.07],[-90.44,16.41],[-90.6,16.47],[-90.71,16.69],[-91.08,16.92],[-91.45,17.25],[-91.0,17.25],[-91.0,17.82],[-90.07,17.82],[-89.14,17.81]]},{"type":"LineString","coordinates":[[-88.93,15.89],[-89.23,15.89],[-89.15,17.02],[-89.14,17.81]]},{"type":"LineString","coordinates":[[-89.35,14.42],[-89.06,14.34],[-88.84,14.14],[-88.54,13.98],[-88.5,13.85],[-88.07,13.96],[-87.86,13.89],[-87.72,13.79],[-87.79,13.38]]},{"type":"LineString","coordinates":[[-87.32,12.98],[-87.01,13.03],[-86.88,13.25],[-86.73,13.26],[-86.76,13.75],[-86.52,13.78],[-86.31,13.77],[-86.1,14.04],[-85.8,13.84],[-85.7,13.96],[-85.51,14.08],[-85.17,14.35],[-85.15,14.56],[-85.05,14.55],[-84.92,14.79],[-84.82,14.82],[-84.65,14.67],[-84.45,14.62],[-84.23,14.75],[-83.98,14.75],[-83.63,14.88],[-83.49,15.02],[-83.15,15.0]]},{"type":"LineString","coordinates":[[-82.97,8.23],[-82.91,8.42],[-82.83,8.63],[-82.87,8.81],[-82.72,8.93],[-82.93,9.07],[-82.93,9.48],[-82.55,9.57]]},{"type":"LineString","coordinates":[[-83.66,10.94],[-83.9,10.73],[-84.19,10.79],[-84.36,11.0],[-84.67,11.08],[-84.9,10.95],[-85.56,11.22],[-85.71,11.09]]},{"type":"LineString","coordinates":[[-77.35,8.67],[-77.47,8.52],[-77.24,7.94],[-77.43,7.64],[-77.75,7.71],[-77.88,7.22]]},{"type":"LineString","coordinates":[[-71.33,11.78],[-71.97,11.61],[-72.23,11.11],[-72.61,10.82],[-72.91,10.45],[-73.03,9.74],[-73.3,9.15],[-72.79,9.09],[-72.66,8.63],[-72.44,8.41],[-72.36,8.0],[-72.48,7.63],[-72.44,7.42],[-72.2,7.34],[-71.96,6.99],[-70.67,7.09],[-70.09,6.96],[-69.39,6.1],[-68.99,6.21],[-68.27,6.15],[-67.7,6.27],[-67.34,6.1],[-67.52,5.56],[-67.74,5.22],[-67.82,4.5],[-67.62,3.84],[-67.34,3.54],[-67.3,3.32],[-67.81,2.82],[-67.45,2.6],[-67.18,2.25],[-66.88,1.25],[-66.88,1.25]]},{"type":"LineString","coordinates":[[-75.37,-0.15],[-75.23,-0.91],[-75.55,-1.56],[-76.64,-2.61],[-77.84,-3.0],[-78.45,-3.87],[-78.64,-4.55],[-79.21,-4.96],[-79.62,-4.45],[-80.03,-4.35],[-80.44,-4.43],[-80.47,-4.06],[-80.18,-3.82],[-80.3,-3.4]]},{"type":"LineString","coordinates":[[-69.53,-10.95],[-70.09,-11.12],[-70.55,-11.01],[-70.48,-9.49],[-71.3,-10.08],[-72.18,-10.05],[-72.56,-9.52],[-73.23,-9.46],[-73.02,-9.03],[-73.57,-8.42],[-73.99,-7.52],[-73.72,-7.34],[-73.72,-6.92],[-73.12,-6.63],[-73.22,-6.09],[-72.96,-5.74],[-72.89,-5.27],[-71.75,-4.59],[-70.93,-4.4],[-70.79,-4.25],[-69.89,-4.3]]},{"type":"LineString","coordinates":[[-66.96,-54.9],[-67.56,-54.87],[-68.63,-54.87],[-68.63,-52.64]]},{"type":"LineString","coordinates":[[-68.63,-52.3],[-69.5,-52.14],[-71.91,-52.01],[-72.33,-51.43],[-72.31,-50.68],[-72.98,-50.74],[-73.33,-50.38],[-73.42,-49.32],[-72.65,-48.88],[-72.33,-48.24],[-72.45,-47.74],[-71.92,-46.88],[-71.55,-45.56],[-71.66,-44.97],[-71.22,-44.78],[-71.33,-44.41],[-71.79,-44.21],[-71.46,-43.79],[-71.92,-43.41],[-72.15,-42.25],[-71.75,-42.05],[-71.92,-40.83],[-71.68,-39.81],[-71.41,-38.92],[-70.81,-38.55],[-71.12,-37.58],[-71.12,-36.66],[-70.36,-36.01],[-70.39,-35.17],[-69.82,-34.19],[-69.81,-33.27],[-70.07,-33.09],[-70.54,-31.36],[-69.92,-30.34],[-70.01,-29.37],[-69.66,-28.46],[-69.0,-27.52],[-68.3,-26.9],[-68.59,-26.51],[-68.39,-26.18],[-68.42,-24.52],[-67.33,-24.03],[-66.99,-22.99],[-67.11,-22.74]]},{"type":"LineString","coordinates":[[-58.43,-33.91],[-58.35,-33.26],[-58.13,-33.04],[-58.14,-32.04],[-57.87,-31.02],[-57.63,-30.22]]},{"type":"LineString","coordinates":[[-54.63,-25.74],[-54.79,-26.62],[-55.7,-27.39],[-56.49,-27.55],[-57.61,-27.4],[-58.62,-27.12],[-57.63,-25.6],[-57.78,-25.16],[-58.81,-24.77],[-60.03,-24.03],[-60.85,-23.88],[-62.69,-22.25]]},{"type":"LineString","coordinates":[[-58.17,-20.18],[-58.17,-20.18],[-57.87,-20.73],[-57.94,-22.09],[-56.88,-22.28],[-56.47,-22.09],[-55.8,-22.36],[-55.61,-22.66],[-55.52,-23.57],[-55.4,-23.96],[-55.03,-24.0],[-54.65,-23.84],[-54.29,-24.02],[-54.29,-24.57],[-54.43,-25.16],[-54.63,-25.74]]},{"type":"LineString","coordinates":[[-59.76,8.37],[-60.55,7.78],[-60.64,7.42],[-60.3,7.04],[-60.54,6.86],[-61.16,6.7],[-61.14,6.23],[-61.41,5.96],[-60.73,5.2]]},{"type":"LineString","coordinates":[[-56.54,1.9],[-56.78,1.86],[-57.34,1.95],[-57.66,1.68],[-58.11,1.51],[-58.43,1.46],[-58.54,1.27],[-59.03,1.32],[-59.65,1.79],[-59.72,2.25],[-59.97,2.76],[-59.82,3.61],[-59.54,3.96],[-59.77,4.42],[-60.11,4.57],[-59.98,5.01],[-60.21,5.24],[-60.73,5.2]]},{"type":"LineString","coordinates":[[-54.52,2.31],[-55.1,2.52],[-55.57,2.42],[-55.97,2.51],[-56.07,2.22],[-55.91,2.02],[-56.0,1.82],[-56.54,1.9]]},{"type":"LineString","coordinates":[[-51.66,4.16],[-52.25,3.24],[-52.56,2.5],[-52.94,2.12],[-53.42,2.05],[-53.55,2.33],[-53.78,2.38],[-54.09,2.11],[-54.52,2.31]]},{"type":"LineString","coordinates":[[115.45,5.45],[115.41,4.96],[115.35,4.32],[114.87,4.35],[114.66,4.01],[114.2,4.53]]},{"type":"LineString","coordinates":[[-6.2,53.87],[-6.95,54.07],[-7.57,54.06],[-7.37,54.6],[-7.57,55.13]]},{"type":"LineString","coordinates":[[-16.71,13.59],[-15.62,13.62],[-15.4,13.86],[-15.08,13.88],[-14.69,13.63],[-14.38,13.63],[-14.05,13.79],[-13.84,13.51],[-14.28,13.28],[-14.71,13.3],[-15.14,13.51],[-15.51,13.28],[-15.69,13.27],[-15.93,13.13],[-16.84,13.15]]},{"type":"LineString","coordinates":[[35.55,32.39],[35.55,31.78],[35.4,31.49]]},{"type":"LineString","coordinates":[[28.98,-28.96],[29.33,-29.26],[29.02,-29.74],[28.85,-30.07],[28.29,-30.23],[28.11,-30.55],[27.75,-30.65],[27.0,-29.88],[27.53,-29.24],[28.07,-28.85],[28.54,-28.65],[28.98,-28.96]]},{"type":"LineString","coordinates":[[-8.67,27.66],[-8.67,27.59],[-8.68,27.4],[-8.69,25.88],[-11.97,25.93],[-11.94,23.37],[-12.87,23.28],[-13.12,22.77],[-12.93,21.33],[-16.85,21.33],[-17.06,21.0]]},{"type":"LineString","coordinates":[[-8.68,27.4],[-4.92,24.97],[-4.92,24.97]]},{"type":"LineString","coordinates":[[-12.17,14.62],[-12.12,13.99],[-11.93,13.42],[-11.55,13.14],[-11.47,12.75],[-11.51,12.44]]},{"type":"LineString","coordinates":[[-13.7,12.59],[-13.72,12.25],[-13.83,12.14],[-13.74,11.81],[-13.9,11.68],[-14.12,11.68],[-14.38,11.51],[-14.69,11.53],[-15.13,11.04]]},{"type":"LineString","coordinates":[[-11.51,12.44],[-11.46,12.08],[-11.3,12.08],[-11.04,12.21],[-10.87,12.18],[-10.59,11.92],[-10.17,11.84],[-9.89,12.06],[-9.57,12.19],[-9.33,12.33],[-9.13,12.31],[-8.91,12.09],[-8.79,11.81],[-8.38,11.39],[-8.58,11.14],[-8.62,10.81],[-8.41,10.91],[-8.28,10.79],[-8.34,10.49],[-8.03,10.21],[-8.03,10.21]]},{"type":"LineString","coordinates":[[-11.44,6.79],[-11.2,7.11],[-11.15,7.4],[-10.7,7.94],[-10.23,8.41]]},{"type":"LineString","coordinates":[[-8.44,7.69],[-8.49,7.4],[-8.39,6.91],[-8.6,6.47],[-8.31,6.19],[-7.99,6.13],[-7.57,5.71],[-7.54,5.31],[-7.64,5.19],[-7.71,4.36]]},{"type":"LineString","coordinates":[[8.42,36.95],[8.22,36.43],[8.38,35.48],[8.14,34.66],[7.52,34.1],[7.61,33.34],[8.43,32.75],[8.44,32.51],[9.06,32.1],[9.48,30.31],[9.48,30.31]]},{"type":"LineString","coordinates":[[11.49,33.14],[11.43,32.37],[10.94,32.08],[10.64,31.76],[9.95,31.38],[10.06,30.96],[9.97,30.54],[9.48,30.31]]},{"type":"LineString","coordinates":[[25.16,31.57],[24.8,31.09],[24.96,30.66],[24.7,30.04],[25,29.24],[25,25.68],[25,22],[25,22]]},{"type":"LineString","coordinates":[[4.27,19.16],[4.27,16.85],[3.72,16.18],[3.64,15.57],[2.75,15.41],[1.39,15.32],[1.02,14.97],[0.37,14.93],[0.37,14.93]]},{"type":"LineString","coordinates":[[0.37,14.93],[0.3,14.44],[0.43,13.99],[0.99,13.34],[1.02,12.85],[2.18,12.63],[2.15,11.94],[2.15,11.94]]},{"type":"LineString","coordinates":[[-2.86,4.99],[-2.81,5.39],[-3.24,6.25],[-2.98,7.38],[-2.56,8.22],[-2.83,9.64]]},{"type":"LineString","coordinates":[[1.06,5.93],[0.84,6.28],[0.57,6.91],[0.49,7.41],[0.71,8.31],[0.46,8.68],[0.37,9.46],[0.37,10.19],[-0.05,10.71],[0.02,11.02]]},{"type":"LineString","coordinates":[[0.9,11.0],[0.77,10.47],[1.08,10.18],[1.43,9.83],[1.46,9.33],[1.66,9.13],[1.62,6.83],[1.87,6.14]]},{"type":"LineString","coordinates":[[2.15,11.94],[2.49,12.23],[2.85,12.24],[3.61,11.66],[3.61,11.66]]},{"type":"LineString","coordinates":[[3.61,11.66],[3.68,12.55],[3.97,12.96],[4.11,13.53],[4.37,13.75],[5.44,13.87],[6.45,13.49],[6.82,13.12],[7.33,13.1],[7.8,13.34],[9.01,12.83],[9.52,12.85],[10.11,13.28],[10.7,13.25],[10.99,13.39],[11.53,13.33],[12.3,13.04],[13.08,13.6],[13.32,13.56]]},{"type":"LineString","coordinates":[[36.87,22],[32.9,22],[29.02,22],[25,22]]},{"type":"LineString","coordinates":[[14.85,22.86],[15.1,21.31],[15.47,21.05],[15.49,20.73],[15.9,20.39],[15.69,19.96],[15.3,17.93],[15.25,16.63],[13.97,15.68],[13.54,14.37],[13.96,14.0],[13.95,13.35],[14.6,13.33],[14.5,12.86],[14.5,12.86]]},{"type":"LineString","coordinates":[[23.84,19.58],[23.89,15.61],[23.02,15.68],[22.57,14.94],[22.3,14.33],[22.51,14.09],[22.18,13.79],[22.3,13.37],[22.04,12.96],[21.94,12.59],[22.29,12.65],[22.5,12.26],[22.51,11.68],[22.88,11.38],[22.86,11.14]]},{"type":"LineString","coordinates":[[15.28,7.42],[14.78,6.41],[14.54,6.23],[14.46,5.45],[14.56,5.03],[14.48,4.73],[14.95,4.21],[15.04,3.85],[15.41,3.34],[15.86,3.01],[15.91,2.56],[16.01,2.27],[16.01,2.27]]},{"type":"LineString","coordinates":[[11.28,2.26],[11.29,1.06],[9.83,1.07],[9.49,1.01]]},{"type":"LineString","coordinates":[[13.08,2.27],[13.0,1.83],[13.28,1.31],[14.03,1.4],[14.28,1.2],[13.84,0.04],[14.32,-0.55],[14.43,-1.33],[14.3,-2.0],[13.99,-2.47],[13.11,-2.43],[12.58,-1.95],[12.5,-2.39],[11.82,-2.51],[11.48,-2.77],[11.86,-3.43],[11.09,-3.98]]},{"type":"LineString","coordinates":[[16.01,2.27],[16.54,3.2],[17.13,3.73],[17.81,3.56],[18.45,3.5]]},{"type":"LineString","coordinates":[[22.86,11.14],[22.98,10.71],[23.55,10.09],[23.56,9.68],[23.39,9.27],[23.46,8.95],[23.81,8.67],[23.81,8.67]]},{"type":"LineString","coordinates":[[36.43,14.42],[37.59,14.21],[37.91,14.96],[38.51,14.51],[39.1,14.74],[39.34,14.53],[40.03,14.52],[40.9,14.12],[41.16,13.77],[41.6,13.45],[42.01,12.87],[42.35,12.54],[42.35,12.54]]},{"type":"LineString","coordinates":[[43.15,11.46],[42.78,10.93],[42.78,10.93]]},{"type":"LineString","coordinates":[[29.58,-1.34],[29.82,-1.44],[30.42,-1.13],[30.42,-1.13]]},{"type":"LineString","coordinates":[[30.42,-1.13],[30.82,-1.7],[30.76,-2.29],[30.47,-2.41]]},{"type":"LineString","coordinates":[[23.91,-10.93],[24.02,-11.24],[23.9,-11.72],[24.08,-12.19],[23.93,-12.57],[24.02,-12.91],[21.93,-12.9],[21.89,-16.08],[22.56,-16.9],[23.22,-17.52]]},{"type":"LineString","coordinates":[[23.22,-17.52],[24.03,-17.3],[24.68,-17.35],[25.08,-17.58],[25.08,-17.66],[24.93,-17.72]]},{"type":"LineString","coordinates":[[32.07,-26.73],[31.87,-27.18],[31.28,-27.29],[30.69,-26.74],[30.68,-26.4],[30.95,-26.02],[31.04,-25.73],[31.33,-25.66],[31.84,-25.84]]},{"type":"LineString","coordinates":[[25.26,-17.74],[25.65,-18.54],[25.85,-18.71],[26.16,-19.29],[27.3,-20.39],[27.72,-20.5],[27.73,-20.85],[28.02,-21.49],[28.79,-21.64],[29.43,-22.09],[29.43,-22.09]]},{"type":"LineString","coordinates":[[30.27,-15.51],[30.34,-15.88],[31.17,-15.86],[31.64,-16.07],[31.85,-16.32],[32.33,-16.39],[32.85,-16.71],[32.85,-17.98],[32.65,-18.67],[32.61,-19.42],[32.77,-19.72],[32.66,-20.3],[32.51,-20.4],[32.25,-21.12],[31.19,-22.25]]},{"type":"LineString","coordinates":[[32.83,-26.74],[32.07,-26.73]]},{"type":"LineString","coordinates":[[-9.03,41.88],[-8.67,42.13],[-8.26,42.28],[-8.01,41.79],[-7.42,41.79],[-7.25,41.92],[-6.67,41.88],[-6.39,41.38],[-6.85,41.11],[-6.86,40.33],[-7.03,40.18],[-7.07,39.71],[-7.5,39.63],[-7.1,39.03],[-7.37,38.37],[-7.03,38.08],[-7.17,37.8],[-7.54,37.43],[-7.45,37.1]]},{"type":"LineString","coordinates":[[2.99,42.47],[1.83,42.34],[0.7,42.8],[0.34,42.58],[-1.5,43.03],[-1.9,43.42]]},{"type":"LineString","coordinates":[[9.92,54.98],[9.28,54.83],[8.53,54.96]]},{"type":"LineString","coordinates":[[6.19,49.46],[6.66,49.2],[8.1,49.02],[7.59,48.33],[7.47,47.62],[7.47,47.62]]},{"type":"LineString","coordinates":[[14.12,53.76],[14.35,53.25],[14.07,52.98],[14.44,52.62],[14.69,52.09],[14.61,51.75],[15.02,51.11],[15.02,51.11]]},{"type":"LineString","coordinates":[[9.59,47.53],[9.63,47.35],[9.48,47.1],[9.93,46.92],[10.44,46.89]]},{"type":"LineString","coordinates":[[10.44,46.89],[11.05,46.75],[11.16,46.94],[12.15,47.12],[12.38,46.77],[13.81,46.51]]},{"type":"LineString","coordinates":[[19.66,54.43],[20.89,54.31],[22.73,54.33],[23.24,54.22],[23.48,53.91],[23.53,53.47],[23.8,53.09],[23.8,52.69],[23.2,52.49],[23.51,52.02],[23.53,51.58],[24.03,50.71],[23.92,50.42],[23.43,50.31],[22.52,49.48],[22.78,49.03],[22.56,49.09],[21.61,49.47],[20.89,49.33],[20.42,49.43],[19.83,49.22],[19.32,49.57],[18.91,49.44],[18.85,49.5],[18.39,49.99],[17.65,50.05],[17.55,50.36],[16.87,50.47],[16.72,50.22],[16.18,50.42],[16.24,50.7],[15.49,50.78],[15.02,51.11]]},{"type":"LineString","coordinates":[[24.31,57.79],[25.16,57.97],[25.6,57.85],[26.46,57.48],[27.29,57.47],[27.29,57.47]]},{"type":"LineString","coordinates":[[27.98,59.48],[28.13,59.3],[27.42,58.72],[27.72,57.79],[27.29,57.47]]},{"type":"LineString","coordinates":[[28.18,56.17],[29.23,55.92],[29.37,55.67],[29.9,55.79],[30.87,55.55],[30.97,55.08],[30.76,54.81],[31.38,54.16],[31.79,53.97],[31.73,53.79],[32.41,53.62],[32.69,53.35],[32.3,53.13],[31.5,53.17],[31.31,53.07],[31.54,52.74],[31.79,52.1],[31.79,52.1]]},{"type":"LineString","coordinates":[[6.91,53.48],[7.09,53.14],[6.84,52.23],[6.59,51.85],[5.99,51.85],[6.16,50.8]]},{"type":"LineString","coordinates":[[6.16,50.8],[6.04,50.13]]},{"type":"LineString","coordinates":[[6.04,50.13],[6.24,49.9],[6.19,49.46],[6.19,49.46]]},{"type":"LineString","coordinates":[[21.27,55.19],[22.32,55.02],[22.76,54.86],[22.65,54.58],[22.73,54.33]]},{"type":"LineString","coordinates":[[21.06,56.03],[22.2,56.34],[23.88,56.27],[24.86,56.37],[25.0,56.16],[25.53,56.1],[26.49,55.62]]},{"type":"LineString","coordinates":[[18.85,49.5],[18.55,49.49],[18.4,49.31],[18.17,49.27],[18.11,49.04],[17.91,49.0],[17.89,48.9],[17.55,48.8],[17.1,48.82],[16.96,48.6]]},{"type":"LineString","coordinates":[[16.96,48.6],[16.88,48.47],[16.98,48.12]]},{"type":"LineString","coordinates":[[22.09,48.42],[22.64,48.15],[22.71,47.88],[22.1,47.67],[21.63,46.99],[21.02,46.32],[20.22,46.13],[19.6,46.17],[18.83,45.91],[18.46,45.76],[17.63,45.95],[16.88,46.38],[16.56,46.5],[16.37,46.84],[16.2,46.85]]},{"type":"LineString","coordinates":[[18.45,42.48],[18.56,42.65],[17.67,43.03],[17.3,43.45],[16.92,43.67],[16.46,44.04],[16.24,44.35],[15.75,44.82],[15.96,45.23],[16.32,45.0],[16.53,45.21],[17.0,45.23],[17.86,45.07],[18.55,45.08],[19.01,44.86]]},{"type":"LineString","coordinates":[[22.56,49.09],[22.28,48.83],[22.09,48.42],[21.87,48.32],[20.8,48.62],[20.47,48.56],[20.24,48.33],[19.77,48.2],[19.66,48.27],[19.17,48.11],[18.78,48.08],[18.7,47.88],[17.86,47.76],[17.49,47.87],[16.98,48.12]]},{"type":"LineString","coordinates":[[16.56,46.5],[15.77,46.24],[15.67,45.83],[15.32,45.73],[15.33,45.45],[14.94,45.47],[14.6,45.63],[14.41,45.47],[13.72,45.5]]},{"type":"LineString","coordinates":[[26.62,48.22],[26.92,48.12],[27.23,47.83],[27.55,47.41],[28.13,46.81],[28.16,46.37],[28.05,45.94],[28.23,45.49]]},{"type":"LineString","coordinates":[[28.56,43.71],[27.97,43.81],[27.24,44.18],[26.07,43.94],[25.57,43.69],[24.1,43.74],[23.33,43.9],[22.94,43.82],[22.66,44.23],[22.66,44.23]]},{"type":"LineString","coordinates":[[22.66,44.23],[22.41,44.01],[22.5,43.64],[22.99,43.21],[22.6,42.9],[22.44,42.58],[22.54,42.46],[22.38,42.32],[22.36,42.32]]},{"type":"LineString","coordinates":[[22.71,47.88],[23.14,48.1],[23.76,47.99],[24.4,47.98],[24.87,47.74],[25.21,47.89],[25.95,47.99],[26.2,48.22],[26.62,48.22]]},{"type":"LineString","coordinates":[[31.79,52.1],[32.16,52.06],[32.41,52.29],[32.72,52.24],[33.75,52.34],[34.39,51.77],[34.14,51.57],[34.22,51.26],[35.02,51.21],[35.38,50.77],[35.36,50.58],[36.63,50.23],[37.39,50.38],[38.01,49.92],[38.6,49.93],[40.07,49.6],[40.08,49.31],[39.67,48.78],[39.9,48.23],[39.74,47.9],[38.77,47.83],[38.26,47.55],[38.22,47.1]]},{"type":"LineString","coordinates":[[28.0,42.01],[27.14,42.14],[26.12,41.83],[26.11,41.33],[25.2,41.23],[24.49,41.58],[23.69,41.31],[22.95,41.34],[22.88,42.0],[22.38,42.32]]},{"type":"LineString","coordinates":[[22.95,41.34],[22.76,41.3],[22.6,41.13],[22.06,41.15],[21.67,40.93],[21.02,40.84]]},{"type":"LineString","coordinates":[[26.12,41.83],[26.6,41.56],[26.29,40.94],[26.06,40.82]]},{"type":"LineString","coordinates":[[21.02,40.84],[21,40.58],[20.68,40.44],[20.61,40.11],[20.15,39.62]]},{"type":"LineString","coordinates":[[41.55,41.54],[42.62,41.58],[43.58,41.09]]},{"type":"LineString","coordinates":[[39.95,43.44],[40.08,43.55],[40.92,43.38],[42.39,43.22],[43.76,42.74],[43.93,42.55],[44.54,42.71],[45.47,42.5],[45.78,42.09],[46.4,41.86],[46.15,41.72],[46.64,41.18],[46.5,41.06],[45.96,41.12],[45.22,41.41],[44.97,41.25]]},{"type":"LineString","coordinates":[[28.59,69.06],[28.45,68.36],[29.98,67.7],[29.05,66.94],[30.22,65.81],[29.54,64.95],[30.44,64.2],[30.04,63.55],[31.52,62.87],[31.14,62.36],[30.21,61.78],[28.07,60.5]]},{"type":"LineString","coordinates":[[35.4,31.48],[35.42,31.1],[34.92,29.5]]},{"type":"LineString","coordinates":[[38.79,33.38],[39.2,32.16],[39.2,32.16]]},{"type":"LineString","coordinates":[[47.97,29.98],[47.3,30.06],[46.57,29.1],[46.57,29.1]]},{"type":"LineString","coordinates":[[48.57,29.93],[48.01,30.45],[48.0,30.99],[47.69,30.98],[47.85,31.71],[47.33,32.47],[46.11,33.02],[45.42,33.97],[45.65,34.75],[46.15,35.09],[46.08,35.68],[45.42,35.98],[44.77,37.17]]},{"type":"LineString","coordinates":[[53.92,37.2],[54.8,37.39],[55.51,37.96],[56.18,37.94],[56.62,38.12],[57.33,38.03],[58.44,37.52],[59.23,37.41],[60.38,36.53],[61.12,36.49],[61.21,35.65]]},{"type":"LineString","coordinates":[[53.11,16.65],[52.78,17.35],[52,19],[52,19]]},{"type":"LineString","coordinates":[[42.78,16.35],[43.22,16.67],[43.12,17.09],[43.38,17.58],[43.79,17.32],[44.06,17.41],[45.22,17.43],[45.4,17.33],[46.37,17.23],[46.75,17.28],[47,16.95],[47.47,17.12],[48.18,18.17],[49.12,18.62],[52,19]]},{"type":"LineString","coordinates":[[20.65,69.11],[21.98,68.62],[23.54,67.94],[23.57,66.4],[23.9,66.01]]},{"type":"LineString","coordinates":[[48.42,28.55],[47.71,28.53],[47.46,29.0],[46.57,29.1]]},{"type":"LineString","coordinates":[[51.39,24.63],[51.11,24.56],[50.81,24.75]]},{"type":"LineString","coordinates":[[56.07,26.06],[56.26,25.71]]},{"type":"LineString","coordinates":[[56.4,24.92],[55.89,24.92],[55.8,24.27],[55.98,24.13],[55.53,23.93],[55.53,23.52],[55.23,23.11],[55.21,22.71],[55.21,22.71]]},{"type":"LineString","coordinates":[[49.1,46.4],[48.59,46.56],[48.69,47.08],[48.06,47.74],[47.32,47.72],[46.47,48.39],[47.04,49.15],[46.75,49.36],[47.55,50.45],[48.58,49.87],[48.7,50.61],[50.77,51.69],[52.33,51.72],[54.53,51.03],[55.72,50.62],[56.78,51.04],[58.36,51.06],[59.64,50.55],[59.93,50.84],[61.34,50.8],[61.59,51.27],[59.97,51.96],[60.93,52.45],[60.74,52.72],[61.7,52.98],[60.98,53.67],[61.44,54.01],[65.18,54.35],[65.67,54.6],[68.17,54.97],[69.07,55.39],[70.87,55.17],[71.18,54.13],[72.22,54.38],[73.51,54.04],[73.43,53.49],[74.38,53.55],[76.89,54.49],[76.53,54.18],[77.8,53.4],[80.04,50.86],[80.57,51.39],[81.95,50.81],[83.38,51.07],[83.94,50.89],[84.42,50.31],[85.12,50.12],[85.54,49.69],[86.83,49.83],[87.36,49.22]]},{"type":"LineString","coordinates":[[70.96,42.27],[71.26,42.17],[70.42,41.52],[71.16,41.14],[71.87,41.39],[73.06,40.87],[71.77,40.15],[71.01,40.24],[70.6,40.22],[70.46,40.5],[70.67,40.96],[69.33,40.73],[69.01,40.09],[68.54,39.53],[67.7,39.58],[67.44,39.14],[68.18,38.9],[68.39,38.16],[67.83,37.15],[67.83,37.15]]},{"type":"LineString","coordinates":[[66.52,37.36],[66.22,37.39],[65.75,37.66],[65.59,37.31],[64.75,37.11],[64.55,36.31],[63.98,36.01],[63.19,35.86],[62.98,35.4],[62.23,35.27],[61.21,35.65]]},{"type":"LineString","coordinates":[[67.83,37.15],[68.14,37.02],[68.86,37.34],[69.2,37.15],[69.52,37.61],[70.12,37.59],[70.27,37.74],[70.38,38.14],[70.81,38.49],[71.35,38.26],[71.24,37.95],[71.54,37.91],[71.45,37.07],[71.84,36.74],[72.19,36.95],[72.64,37.05],[73.26,37.5],[73.95,37.42],[74.98,37.42]]},{"type":"LineString","coordinates":[[80.26,42.35],[80.12,42.12],[78.54,41.58],[78.19,41.19],[76.9,41.07],[76.53,40.43],[75.47,40.56],[74.78,40.37],[73.82,39.89],[73.96,39.66],[73.68,39.43]]},{"type":"LineString","coordinates":[[73.68,39.43],[73.93,38.51],[74.26,38.61],[74.86,38.38],[74.83,37.99],[74.98,37.42]]},{"type":"LineString","coordinates":[[74.45,32.76],[75.26,32.27],[75.26,32.27]]},{"type":"LineString","coordinates":[[97.33,28.26],[97.4,27.88],[97.05,27.7],[97.13,27.08],[96.42,27.26],[95.12,26.57],[95.16,26.0],[94.6,25.16],[94.55,24.68],[94.11,23.85],[93.33,24.08],[93.29,23.04],[93.06,22.7],[93.17,22.28],[92.67,22.04]]},{"type":"LineString","coordinates":[[88.12,27.88],[86.95,27.97],[85.82,28.2],[85.01,28.64],[84.23,28.84],[83.9,29.32],[83.34,29.46],[82.33,30.12],[81.53,30.42],[81.11,30.18]]},{"type":"LineString","coordinates":[[88.81,27.3],[89.48,28.04],[90.02,28.3],[90.73,28.06],[91.26,28.04],[91.7,27.77]]},{"type":"LineString","coordinates":[[92.37,20.67],[92.3,21.48],[92.65,21.32],[92.67,22.04]]},{"type":"LineString","coordinates":[[97.33,28.26],[97.91,28.34],[98.25,27.75],[98.68,27.51],[98.71,26.74],[98.67,25.92],[97.72,25.08],[97.6,23.9],[98.66,24.06],[98.9,23.14],[99.53,22.95],[99.24,22.12],[99.98,21.74],[100.42,21.56],[101.15,21.85],[101.18,21.44],[101.18,21.44]]},{"type":"LineString","coordinates":[[100.12,20.42],[100.55,20.11],[100.61,19.51],[101.28,19.46],[101.04,18.41],[101.06,17.51],[102.11,18.11],[102.41,17.93],[103.0,17.96],[103.2,18.31],[103.96,18.24],[104.72,17.43],[104.78,16.44],[105.59,15.57],[105.54,14.72],[105.22,14.27],[105.22,14.27],[104.28,14.42],[102.99,14.23],[102.35,13.39],[102.58,12.19]]},{"type":"LineString","coordinates":[[101.18,21.44],[101.27,21.2],[101.8,21.17],[101.65,22.32],[102.17,22.46]]},{"type":"LineString","coordinates":[[107.38,14.2],[107.61,13.54],[107.49,12.34],[105.81,11.57],[106.25,10.96],[105.2,10.89],[104.33,10.49]]},{"type":"LineString","coordinates":[[102.17,22.46],[102.71,22.71],[103.5,22.7],[104.48,22.82],[105.33,23.35],[105.81,22.98],[106.73,22.79],[106.57,22.22],[107.04,21.81],[108.05,21.55]]},{"type":"LineString","coordinates":[[102.14,6.22],[101.81,5.81],[101.15,5.69],[101.08,6.2],[100.26,6.64],[100.09,6.46]]},{"type":"LineString","coordinates":[[117.88,4.14],[117.02,4.31],[115.87,4.31],[115.52,3.17],[115.13,2.82],[114.62,1.43],[113.81,1.22],[112.86,1.5],[112.38,1.41],[111.8,0.9],[111.16,0.98],[110.51,0.77],[109.83,1.34],[109.66,2.01]]},{"type":"LineString","coordinates":[[87.75,49.3],[88.81,49.47],[90.71,50.33],[92.23,50.8],[93.1,50.5],[94.15,50.48],[94.82,50.01],[95.81,49.98],[97.26,49.73],[98.23,50.42],[97.83,51.01],[98.86,52.05],[99.98,51.63],[100.89,51.52],[102.07,51.26],[102.26,50.51],[103.68,50.09],[104.62,50.28],[105.89,50.41],[106.89,50.27],[107.87,49.79],[108.48,49.28],[109.4,49.29],[110.66,49.13],[111.58,49.38],[112.9,49.54],[114.36,50.25],[114.96,50.14],[115.49,49.81],[116.68,49.89]]},{"type":"LineString","coordinates":[[124.27,39.93],[125.08,40.57],[126.18,41.11],[126.87,41.82],[127.34,41.5],[128.21,41.47],[128.05,41.99],[129.6,42.42],[129.99,42.99],[130.64,42.4]]},{"type":"LineString","coordinates":[[128.35,38.61],[128.21,38.37],[127.78,38.3],[127.07,38.26],[126.68,37.8],[126.24,37.84],[126.17,37.75]]},{"type":"LineString","coordinates":[[124.97,-8.89],[125.07,-9.09],[125.09,-9.39]]},{"type":"LineString","coordinates":[[141.0,-2.6],[141.02,-5.86],[141.03,-9.12]]},{"type":"LineString","coordinates":[[11.91,-5.04],[12.32,-4.61],[12.62,-4.44],[13.0,-4.78]]},{"type":"LineString","coordinates":[[19.01,44.86],[19.37,44.86],[19.12,44.42],[19.6,44.04],[19.45,43.57],[19.22,43.52]]},{"type":"LineString","coordinates":[[48.58,41.81],[47.99,41.41],[47.82,41.15],[47.37,41.22],[46.69,41.83],[46.4,41.86]]},{"type":"LineString","coordinates":[[42.35,37.23],[41.84,36.61],[41.29,36.36],[41.38,35.63],[41.01,34.42],[38.79,33.38],[38.79,33.38]]},{"type":"LineString","coordinates":[[35.82,33.28],[35.55,33.26],[35.55,33.26]]},{"type":"LineString","coordinates":[[32.73,35.14],[32.92,35.09],[33.19,35.17],[33.38,35.16],[33.46,35.1],[33.48,35.0],[33.53,35.04],[33.68,35.02],[33.87,35.09],[33.97,35.06]]},{"type":"LineString","coordinates":[[19.22,43.52],[19.48,43.35],[19.63,43.21],[19.96,43.11],[20.34,42.9],[20.26,42.81]]},{"type":"LineString","coordinates":[[44.97,41.25],[45.18,40.99],[45.56,40.81],[45.36,40.56],[45.89,40.22],[45.61,39.9],[46.03,39.63],[46.48,39.46],[46.51,38.77],[46.51,38.77]]},{"type":"LineString","coordinates":[[-122.84,49],[-120,49],[-117.03,49],[-116.05,49],[-113,49],[-110.05,49],[-107.05,49],[-104.05,49.0],[-100.65,49],[-97.23,49.0],[-95.16,49],[-95.16,49.38],[-94.82,49.39],[-94.64,48.84],[-94.33,48.67],[-93.63,48.61],[-92.61,48.45],[-91.64,48.14],[-90.83,48.27],[-89.6,48.01],[-89.27,48.02],[-88.38,48.3],[-87.44,47.94],[-86.46,47.55],[-85.65,47.22],[-84.88,46.9],[-84.78,46.64],[-84.54,46.54],[-84.6,46.44],[-84.34,46.41],[-84.14,46.51],[-84.09,46.28],[-83.89,46.12],[-83.62,46.12],[-83.47,45.99],[-83.59,45.82],[-82.55,45.35],[-82.34,44.44],[-82.14,43.57],[-82.43,42.98],[-82.9,42.43],[-83.12,42.08],[-83.14,41.98],[-83.03,41.83],[-82.69,41.68],[-82.44,41.68],[-81.28,42.21],[-80.25,42.37],[-78.94,42.86],[-78.92,42.97],[-79.01,43.27],[-79.17,43.47],[-78.72,43.63],[-77.74,43.63],[-76.82,43.63],[-76.5,44.02],[-76.38,44.1],[-75.32,44.82],[-74.87,45.0],[-73.35,45.01],[-71.51,45.01],[-71.41,45.26],[-71.08,45.31],[-70.66,45.46],[-70.31,45.91],[-70.0,46.69],[-69.24,47.45],[-68.91,47.19],[-68.23,47.35],[-67.79,47.07],[-67.79,45.7],[-67.14,45.14]]},{"type":"LineString","coordinates":[[38.41,18.0],[37.9,17.43],[37.17,17.26],[36.85,16.96],[36.75,16.29],[36.32,14.82],[36.43,14.42],[36.43,14.42]]},{"type":"LineString","coordinates":[[47.79,8.0],[44.96,5.0],[43.66,4.96],[42.77,4.25],[42.13,4.23],[41.86,3.92]]},{"type":"LineString","coordinates":[[41.86,3.92],[41.17,3.92],[40.77,4.26],[39.85,3.84],[39.56,3.42],[38.89,3.5],[38.67,3.62],[38.44,3.59],[38.12,3.6],[36.86,4.45],[36.16,4.45],[35.82,4.78]]},{"type":"LineString","coordinates":[[39.2,-4.68],[37.77,-3.68],[37.7,-3.1],[34.07,-1.06],[33.9,-0.95]]},{"type":"LineString","coordinates":[[-8.67,27.66],[-8.82,27.66],[-8.79,27.12],[-9.41,27.09],[-9.74,26.86],[-10.19,26.86],[-10.55,26.99],[-11.39,26.88],[-11.72,26.1],[-12.03,26.03],[-12.5,24.77],[-13.89,23.69],[-14.22,22.31],[-14.63,21.86],[-14.75,21.5],[-17.0,21.42]]},{"type":"LineString","coordinates":[[11.03,58.86],[11.47,59.43],[12.3,60.12],[12.63,61.29],[11.99,61.8],[11.93,63.13],[12.58,64.07],[13.57,64.05],[13.92,64.45],[13.56,64.79],[15.11,66.19],[16.11,67.3],[16.77,68.01],[17.73,68.01],[17.99,68.57],[19.88,68.41],[20.03,69.07],[20.65,69.11],[20.65,69.11]]},{"type":"LineString","coordinates":[[40.32,-10.32],[39.52,-10.9],[38.43,-11.29],[37.83,-11.27],[37.47,-11.57],[36.78,-11.59],[36.51,-11.72],[35.31,-11.44],[34.56,-11.52]]},{"type":"LineString","coordinates":[[32.76,-9.23],[33.23,-9.68],[33.49,-10.53],[33.32,-10.8],[33.11,-11.61],[33.31,-12.44],[32.99,-12.78],[32.69,-13.71],[33.21,-13.97],[33.21,-13.97]]},{"type":"LineString","coordinates":[[34.56,-11.52],[34.28,-12.28],[34.56,-13.58],[34.91,-13.57],[35.27,-13.89],[35.69,-14.61],[35.77,-15.9],[35.34,-16.11],[35.03,-16.8],[34.38,-16.18],[34.31,-15.48],[34.52,-15.01],[34.46,-14.61],[34.06,-14.36],[33.79,-14.45],[33.21,-13.97]]},{"type":"LineString","coordinates":[[30.83,3.51],[30.77,2.34],[31.17,2.2],[30.85,1.85],[30.47,1.58],[30.09,1.06],[29.88,0.6],[29.82,-0.21],[29.59,-0.59],[29.58,-1.34]]},{"type":"LineString","coordinates":[[30.47,-2.41],[30.53,-2.81],[30.74,-3.03],[30.75,-3.36],[30.51,-3.57],[30.12,-4.09],[29.75,-4.45],[29.34,-4.5]]},{"type":"LineString","coordinates":[[20.26,42.81],[20.5,42.88],[20.64,43.22],[20.81,43.27],[20.96,43.13],[21.14,43.07],[21.27,42.91],[21.44,42.86],[21.63,42.68],[21.78,42.68],[21.66,42.44],[21.54,42.32],[21.58,42.25]]},{"type":"LineString","coordinates":[[-71.71,18.05],[-71.69,18.32],[-71.95,18.62],[-71.7,18.79],[-71.62,19.17],[-71.71,19.71]]},{"type":"LineString","coordinates":[[48.95,11.41],[48.94,11.39],[48.94,10.98],[48.94,9.97],[48.94,9.45],[48.49,8.84],[47.79,8.0]]},{"type":"LineString","coordinates":[[-69.59,-17.58],[-69.86,-18.09],[-70.37,-18.35]]},{"type":"LineString","coordinates":[[34.92,29.5],[34.27,31.22]]},{"type":"LineString","coordinates":[[35.72,32.71],[35.55,32.4]]},{"type":"LineString","coordinates":[[35.82,33.28],[36.07,33.82],[36.61,34.2],[36.45,34.59],[36.0,34.64]]},{"type":"LineString","coordinates":[[35.55,32.4],[35.55,32.39],[35.18,32.53],[34.97,31.87],[35.23,31.75],[34.97,31.62],[34.93,31.35],[35.4,31.49],[35.4,31.48]]},{"type":"LineString","coordinates":[[35.7,32.72],[35.84,32.87],[35.82,33.28],[35.82,33.28]]},{"type":"LineString","coordinates":[[29.0,9.6],[29.52,9.79],[29.62,10.08],[30.0,10.29],[30.84,9.71],[31.35,9.81],[31.85,10.53],[32.4,11.08],[32.31,11.68],[32.07,11.97],[32.67,12.02],[32.74,12.25],[33.21,12.18],[33.09,11.44],[33.21,10.72],[33.72,10.33],[33.84,9.98],[33.82,9.48],[33.96,9.46]]},{"type":"LineString","coordinates":[[23.89,8.62],[24.54,8.92],[24.79,9.81],[25.07,10.27],[25.79,10.41],[25.96,10.14],[26.48,9.55],[26.75,9.47],[27.11,9.64],[27.83,9.6]]},{"type":"LineString","coordinates":[[34.01,4.25],[33.39,3.79],[32.69,3.79],[31.88,3.56],[31.25,3.78],[30.83,3.51],[30.83,3.51]]},{"type":"LineString","coordinates":[[27.83,9.6],[27.97,9.4],[28.97,9.4],[29.0,9.6]]},{"type":"LineString","coordinates":[[35.9,4.61],[34.41,4.63],[34.01,4.25]]},{"type":"LineString","coordinates":[[35.82,4.78],[35.82,5.34],[35.3,5.51]]},{"type":"LineString","coordinates":[[33.94,-9.69],[33.74,-9.42],[32.76,-9.23]]},{"type":"LineString","coordinates":[[34.56,-11.52],[34.28,-10.16],[33.94,-9.69]]},{"type":"LineString","coordinates":[[-2.17,35.17],[-1.79,34.53],[-1.73,33.92],[-1.39,32.86],[-1.12,32.65],[-1.31,32.26]]},{"type":"LineString","coordinates":[[-1.31,32.26],[-2.62,32.09],[-3.07,31.72],[-3.65,31.64],[-3.69,30.9],[-4.86,30.5],[-5.24,30.0],[-6.06,29.73],[-7.06,29.58],[-8.67,28.84],[-8.67,27.66]]},{"type":"LineString","coordinates":[[75.16,37.13],[75.9,36.67],[75.9,36.67]]},{"type":"LineString","coordinates":[[78.81,33.51],[79.21,32.99]]},{"type":"LineString","coordinates":[[77.84,35.49],[78.91,34.32],[78.81,33.51]]},{"type":"LineString","coordinates":[[91.7,27.77],[92.5,27.9],[93.41,28.64],[94.57,29.28],[95.4,29.03],[96.12,29.45],[96.59,28.83],[96.25,28.41],[97.33,28.26]]},{"type":"LineString","coordinates":[[-57.15,5.97],[-57.31,5.07],[-57.91,4.81],[-57.86,4.58],[-58.04,4.06],[-57.6,3.33]]},{"type":"LineString","coordinates":[[-53.96,5.76],[-54.48,4.9],[-54.4,4.21],[-54.01,3.62],[-54.18,3.19]]},{"type":"LineString","coordinates":[[-57.6,3.33],[-57.28,3.33],[-57.15,2.77],[-56.54,1.9]]},{"type":"LineString","coordinates":[[-54.18,3.19],[-54.27,2.73],[-54.52,2.31]]},{"type":"LineString","coordinates":[[42.78,10.93],[42.56,10.57],[42.93,10.02],[43.3,9.54],[43.68,9.18],[46.95,8.0],[47.79,8.0]]},{"type":"LineString","coordinates":[[41.86,3.92],[40.98,2.78],[40.99,-0.86],[41.59,-1.68]]},{"type":"LineString","coordinates":[[77.84,35.49],[77.29,35.05],[77.29,35.05]]},{"type":"LineString","coordinates":[[33.44,45.97],[33.7,46.22],[34.41,46.01],[34.73,45.97],[34.86,45.77],[35.01,45.74]]},{"type":"LineString","coordinates":[[75.26,32.27],[74.41,31.69],[74.42,30.98],[73.45,29.98],[72.82,28.96],[71.78,27.91],[70.62,27.99],[69.51,26.94],[70.17,26.49],[70.28,25.72],[70.84,25.22],[71.04,24.36],[68.84,24.36],[68.18,23.69]]},{"type":"LineString","coordinates":[[-60.73,5.2],[-60.73,5.2],[-60.6,4.92],[-60.97,4.54],[-62.09,4.16],[-62.8,4.01],[-63.09,3.77],[-63.89,4.02],[-64.63,4.15],[-64.82,4.06],[-64.37,3.8],[-64.41,3.13],[-64.27,2.5],[-63.42,2.41],[-63.37,2.2],[-64.08,1.92],[-64.2,1.49],[-64.61,1.33],[-65.35,1.1],[-65.55,0.79],[-66.33,0.72],[-66.88,1.25]]},{"type":"LineString","coordinates":[[44.79,39.71],[44.79,39.71],[45.0,39.74],[45.3,39.47],[45.74,39.47],[45.74,39.32],[46.14,38.74]]},{"type":"LineString","coordinates":[[46.51,38.77],[46.14,38.74]]},{"type":"LineString","coordinates":[[44.97,41.25],[43.58,41.09]]},{"type":"LineString","coordinates":[[43.58,41.09],[43.75,40.74],[43.66,40.25],[44.4,40.01],[44.79,39.71],[44.79,39.71]]},{"type":"LineString","coordinates":[[44.79,39.71],[44.11,39.43],[44.42,38.28],[44.23,37.97],[44.77,37.17]]},{"type":"LineString","coordinates":[[44.77,37.17],[44.77,37.17],[44.29,37.0],[43.94,37.26],[42.78,37.39],[42.35,37.23],[42.35,37.23]]},{"type":"LineString","coordinates":[[42.35,37.23],[41.21,37.07],[40.67,37.09],[39.52,36.72],[38.7,36.71],[38.17,36.9],[37.07,36.62],[36.74,36.82],[36.69,36.26],[36.42,36.04],[36.15,35.82]]},{"type":"LineString","coordinates":[[38.79,33.38],[36.83,32.31],[35.72,32.71],[35.7,32.72]]},{"type":"LineString","coordinates":[[39.2,32.16],[39.0,32.01],[37.0,31.51],[38.0,30.51],[37.67,30.34],[37.5,30.0],[36.74,29.87],[36.5,29.51],[36.07,29.2],[34.96,29.36]]},{"type":"LineString","coordinates":[[46.57,29.1],[44.71,29.18],[41.89,31.19],[40.4,31.89],[39.2,32.16]]},{"type":"LineString","coordinates":[[52,19],[55,20],[55.67,22],[55.21,22.71]]},{"type":"LineString","coordinates":[[55.21,22.71],[55.01,22.5],[52.0,23.0],[51.62,24.01],[51.58,24.25]]},{"type":"LineString","coordinates":[[61.21,35.65],[60.8,34.4],[60.53,33.68],[60.96,33.53],[60.54,32.98],[60.86,32.18],[60.94,31.55],[61.7,31.38],[61.78,30.74],[60.87,29.83]]},{"type":"LineString","coordinates":[[60.87,29.83],[60.87,29.83],[61.37,29.3],[61.77,28.7],[62.73,28.26],[62.76,27.38],[63.23,27.22],[63.32,26.76],[61.87,26.24],[61.5,25.08]]},{"type":"LineString","coordinates":[[67.83,37.15],[67.08,37.36],[66.52,37.36],[66.52,37.36]]},{"type":"LineString","coordinates":[[66.52,37.36],[66.55,37.97],[65.22,38.4],[64.17,38.89],[63.52,39.36],[62.37,40.05],[61.88,41.08],[61.55,41.27],[60.47,41.22],[60.08,41.43],[59.98,42.22],[58.63,42.75],[57.79,42.17],[56.93,41.83],[57.1,41.32],[55.97,41.31]]},{"type":"LineString","coordinates":[[74.98,37.42],[74.98,37.42],[75.16,37.13],[75.16,37.13]]},{"type":"LineString","coordinates":[[75.16,37.13],[74.58,37.02],[74.07,36.84],[72.92,36.72],[71.85,36.51],[71.26,36.07],[71.5,35.65],[71.61,35.15],[71.12,34.73],[71.16,34.35],[70.88,33.99],[69.93,34.02],[70.32,33.36],[69.69,33.11],[69.26,32.5],[69.32,31.9],[68.93,31.62],[68.56,31.71],[67.79,31.58],[67.68,31.3],[66.94,31.3],[66.38,30.74],[66.35,29.89],[65.05,29.47],[64.35,29.56],[64.15,29.34],[63.55,29.47],[62.55,29.32],[60.87,29.83]]},{"type":"LineString","coordinates":[[73.68,39.43],[71.78,39.28],[70.55,39.6],[69.46,39.53],[69.56,40.1],[70.65,39.94],[71.01,40.24]]},{"type":"LineString","coordinates":[[87.36,49.22],[86.6,48.55],[85.77,48.46],[85.72,47.45],[85.16,47.0],[83.18,47.33],[82.46,45.54],[81.95,45.32],[79.97,44.92],[80.87,43.18],[80.18,42.92],[80.26,42.35],[80.26,42.35]]},{"type":"LineString","coordinates":[[80.26,42.35],[79.64,42.5],[79.14,42.86],[77.66,42.96],[76.0,42.99],[75.64,42.88],[74.21,43.3],[73.65,43.09],[73.49,42.5],[71.84,42.85],[71.19,42.7],[70.96,42.27]]},{"type":"LineString","coordinates":[[70.96,42.27],[70.96,42.27],[70.39,42.08],[69.07,41.38],[68.63,40.67],[68.26,40.66],[67.99,41.14],[66.71,41.17],[66.51,41.99],[66.02,41.99],[66.1,43.0],[64.9,43.73],[63.19,43.65],[62.01,43.5],[61.06,44.41],[60.24,44.78],[58.69,45.5],[58.5,45.59],[55.93,45.0],[55.97,41.31],[55.97,41.31]]},{"type":"LineString","coordinates":[[55.97,41.31],[55.46,41.26],[54.76,42.04],[54.08,42.32],[52.94,42.12],[52.5,41.78]]},{"type":"LineString","coordinates":[[76.65,35.79],[77.84,35.49]]},{"type":"LineString","coordinates":[[81.11,30.18],[80.48,29.73],[80.09,28.79],[81.06,28.42],[82,27.93],[83.3,27.36],[84.68,27.23],[85.25,26.73],[86.02,26.63],[87.23,26.4],[88.06,26.41],[88.17,26.81],[88.04,27.45],[88.12,27.88]]},{"type":"LineString","coordinates":[[88.81,27.3],[88.84,27.1],[89.74,26.72],[90.37,26.88],[91.22,26.81],[92.03,26.84],[92.1,27.45],[91.7,27.77]]},{"type":"LineString","coordinates":[[88.12,27.88],[88.12,27.88],[88.73,28.09],[88.73,28.09],[88.81,27.3],[88.81,27.3]]},{"type":"LineString","coordinates":[[92.67,22.04],[92.15,23.63],[91.87,23.62],[91.71,22.99],[91.16,23.5],[91.47,24.07],[91.92,24.13],[92.38,24.98],[91.8,25.15],[90.87,25.13],[89.92,25.27],[89.83,25.97],[89.36,26.01],[88.56,26.45],[88.21,25.77],[88.93,25.24],[88.31,24.87],[88.08,24.5],[88.7,24.23],[88.53,23.63],[88.88,22.88],[89.03,22.06]]},{"type":"LineString","coordinates":[[101.18,21.44],[100.33,20.79],[100.12,20.42],[100.12,20.42]]},{"type":"LineString","coordinates":[[100.12,20.42],[99.54,20.19],[98.96,19.75],[98.25,19.71],[97.8,18.63],[97.38,18.45],[97.86,17.57],[98.49,16.84],[98.9,16.18],[98.54,15.31],[98.19,15.12],[98.43,14.62],[99.1,13.83],[99.21,13.27],[99.2,12.8],[99.59,11.89],[99.04,10.96],[98.55,9.93]]},{"type":"LineString","coordinates":[[102.17,22.46],[102.17,22.46],[102.75,21.68],[103.2,20.77],[104.44,20.76],[104.82,19.89],[104.18,19.62],[103.9,19.27],[105.09,18.67],[105.93,17.49],[106.56,16.6],[107.31,15.91],[107.56,15.2],[107.38,14.2],[107.38,14.2]]},{"type":"LineString","coordinates":[[107.38,14.2],[106.5,14.57],[106.04,13.88],[105.22,14.27]]},{"type":"LineString","coordinates":[[130.64,42.4],[130.78,42.22]]},{"type":"LineString","coordinates":[[27.29,57.47],[27.77,57.24],[27.86,56.76],[28.18,56.17],[28.18,56.17]]},{"type":"LineString","coordinates":[[28.18,56.17],[27.1,55.78],[26.49,55.62]]},{"type":"LineString","coordinates":[[31.79,52.1],[30.93,52.04],[30.62,51.82],[30.56,51.32],[30.16,51.42],[29.25,51.37],[28.99,51.6],[28.62,51.43],[28.24,51.57],[27.45,51.59],[26.34,51.83],[25.33,51.91],[24.55,51.89],[24.01,51.62],[23.53,51.58]]},{"type":"LineString","coordinates":[[26.49,55.62],[26.49,55.62],[26.59,55.17],[25.77,54.85],[25.54,54.28],[24.45,53.91],[23.48,53.91]]},{"type":"LineString","coordinates":[[20.65,69.11],[21.24,69.37],[22.36,68.84],[23.66,68.89],[24.74,68.65],[25.69,69.09],[26.18,69.83],[27.73,70.16],[29.02,69.77],[28.59,69.06],[28.59,69.06]]},{"type":"LineString","coordinates":[[28.59,69.06],[29.4,69.16],[31.1,69.56]]},{"type":"LineString","coordinates":[[26.62,48.22],[26.62,48.22],[26.86,48.37],[27.52,48.47],[28.26,48.16],[28.67,48.12],[29.12,47.85],[29.05,47.51],[29.42,47.35],[29.56,46.93],[29.91,46.67],[29.84,46.53],[30.02,46.42],[29.76,46.35],[29.17,46.38],[29.07,46.52],[28.86,46.44],[28.93,46.26],[28.66,45.94],[28.49,45.6],[28.23,45.49]]},{"type":"LineString","coordinates":[[28.23,45.49],[28.23,45.49],[28.68,45.3],[29.15,45.46],[29.6,45.29]]},{"type":"LineString","coordinates":[[22.66,44.23],[22.47,44.41],[22.71,44.58],[22.46,44.7],[22.15,44.48],[21.56,44.77],[21.48,45.18],[20.87,45.42],[20.76,45.73],[20.22,46.13]]},{"type":"LineString","coordinates":[[21.02,40.84],[21.02,40.84],[20.61,41.09],[20.46,41.52],[20.59,41.86]]},{"type":"LineString","coordinates":[[21.58,42.25],[21.58,42.25],[21.35,42.21],[20.76,42.05],[20.72,41.85],[20.59,41.86]]},{"type":"LineString","coordinates":[[20.59,41.86],[20.59,41.86],[20.52,42.22],[20.28,42.32],[20.07,42.59]]},{"type":"LineString","coordinates":[[20.07,42.59],[20.07,42.59],[19.8,42.5],[19.74,42.69],[19.3,42.2],[19.37,41.88]]},{"type":"LineString","coordinates":[[22.36,42.32],[21.92,42.3],[21.58,42.25]]},{"type":"LineString","coordinates":[[20.26,42.81],[20.26,42.81],[20.07,42.59]]},{"type":"LineString","coordinates":[[19.22,43.52],[19.03,43.43],[18.71,43.2],[18.56,42.65]]},{"type":"LineString","coordinates":[[19.01,44.86],[19.39,45.24],[19.07,45.52],[18.83,45.91]]},{"type":"LineString","coordinates":[[15.02,51.11],[14.57,51.0],[14.31,51.12],[14.06,50.93],[13.34,50.73],[12.97,50.48],[12.24,50.27],[12.42,49.97],[12.52,49.55],[13.03,49.31],[13.6,48.88],[13.6,48.88]]},{"type":"LineString","coordinates":[[13.6,48.88],[13.24,48.42],[12.88,48.29],[13.03,47.64],[12.93,47.47],[12.62,47.67],[12.14,47.7],[11.43,47.52],[10.54,47.57],[10.4,47.3],[9.9,47.58],[9.59,47.53],[9.59,47.53]]},{"type":"LineString","coordinates":[[9.59,47.53],[8.52,47.83],[8.32,47.61],[7.47,47.62]]},{"type":"LineString","coordinates":[[16.96,48.6],[16.5,48.79],[16.03,48.73],[15.25,49.04],[14.9,48.96],[14.34,48.56],[13.6,48.88]]},{"type":"LineString","coordinates":[[16.98,48.12],[16.9,47.71],[16.34,47.71],[16.53,47.5],[16.2,46.85],[16.2,46.85]]},{"type":"LineString","coordinates":[[16.2,46.85],[16.01,46.68],[15.14,46.66],[14.63,46.43],[13.81,46.51]]},{"type":"LineString","coordinates":[[13.81,46.51],[13.7,46.02],[13.94,45.59]]},{"type":"LineString","coordinates":[[10.44,46.89],[10.36,46.48],[9.92,46.31],[9.18,46.44],[8.97,46.04],[8.49,46.01],[8.32,46.16],[7.76,45.82],[7.27,45.78],[6.84,45.99]]},{"type":"LineString","coordinates":[[7.47,47.62],[7.19,47.45],[6.74,47.54],[6.77,47.29],[6.04,46.73],[6.02,46.27],[6.5,46.43],[6.84,45.99]]},{"type":"LineString","coordinates":[[6.84,45.99],[6.84,45.99],[6.8,45.71],[7.1,45.33],[6.75,45.03],[7.01,44.25],[7.55,44.13],[7.44,43.69]]},{"type":"LineString","coordinates":[[6.04,50.13],[6.04,50.13],[5.78,50.09],[5.67,49.53],[5.67,49.53]]},{"type":"LineString","coordinates":[[5.67,49.53],[4.8,49.99],[4.29,49.91],[3.59,50.38],[3.12,50.78],[2.66,50.8],[2.51,51.15]]},{"type":"LineString","coordinates":[[6.16,50.8],[6.16,50.8],[5.61,51.04],[4.97,51.48],[4.05,51.27],[3.31,51.35]]},{"type":"LineString","coordinates":[[-4.92,24.97],[-6.45,24.96],[-5.97,20.64],[-5.49,16.33],[-5.32,16.2],[-5.54,15.5],[-9.55,15.49],[-9.7,15.26],[-10.09,15.33],[-10.65,15.13],[-11.35,15.41],[-11.67,15.39],[-11.83,14.8],[-12.17,14.62],[-12.17,14.62]]},{"type":"LineString","coordinates":[[-12.17,14.62],[-12.83,15.3],[-13.44,16.04],[-14.1,16.3],[-14.58,16.6],[-15.14,16.59],[-15.62,16.37],[-16.12,16.46],[-16.46,16.14]]},{"type":"LineString","coordinates":[[9.48,30.31],[9.81,29.42],[9.86,28.96],[9.68,28.14],[9.76,27.69],[9.63,27.14],[9.72,26.51],[9.32,26.09],[9.91,25.37],[9.95,24.94],[10.3,24.38],[10.77,24.56],[11.56,24.1],[12.0,23.47],[12.0,23.47]]},{"type":"LineString","coordinates":[[12.0,23.47],[8.57,21.57],[5.68,19.6],[4.27,19.16]]},{"type":"LineString","coordinates":[[4.27,19.16],[4.27,19.16],[3.16,19.06],[3.15,19.69],[2.68,19.86],[2.06,20.14],[1.82,20.61],[-1.55,22.79],[-4.92,24.97]]},{"type":"LineString","coordinates":[[14.85,22.86],[14.14,22.49],[13.58,23.04],[12.0,23.47]]},{"type":"LineString","coordinates":[[25,22],[25,20.0],[23.85,20],[23.84,19.58]]},{"type":"LineString","coordinates":[[23.84,19.58],[19.85,21.5],[15.86,23.41],[14.85,22.86]]},{"type":"LineString","coordinates":[[22.86,11.14],[22.23,10.97],[21.72,10.57],[21.0,9.48],[20.06,9.01],[19.09,9.07],[18.81,8.98],[18.91,8.63],[18.39,8.28],[17.96,7.89],[16.71,7.51],[16.46,7.73],[16.29,7.75],[16.11,7.5],[15.28,7.42]]},{"type":"LineString","coordinates":[[15.28,7.42],[15.28,7.42],[15.44,7.69],[15.12,8.38],[14.98,8.8],[14.54,8.97],[13.95,9.55],[14.17,10.02],[14.63,9.92],[14.91,9.99],[15.47,9.98],[14.92,10.89],[14.96,11.56],[14.89,12.22],[14.5,12.86]]},{"type":"LineString","coordinates":[[14.5,12.86],[14.21,12.8],[14.18,12.48]]},{"type":"LineString","coordinates":[[14.18,12.48],[14.58,12.09],[14.47,11.9],[14.42,11.57],[13.57,10.8],[13.31,10.16],[13.17,9.64],[12.96,9.42],[12.75,8.72],[12.22,8.31],[12.06,7.8],[11.84,7.4],[11.75,6.98],[11.06,6.64],[10.5,7.06],[10.12,7.04],[9.52,6.45],[9.23,6.44],[8.76,5.48],[8.5,4.77]]},{"type":"LineString","coordinates":[[-11.51,12.44],[-11.51,12.44],[-11.66,12.39],[-12.2,12.47],[-12.28,12.35],[-12.5,12.33],[-13.22,12.58],[-13.7,12.59]]},{"type":"LineString","coordinates":[[-8.03,10.21],[-8.23,10.13],[-8.31,9.79],[-8.08,9.38],[-7.83,8.58],[-8.2,8.46],[-8.3,8.32],[-8.22,8.12],[-8.28,7.69],[-8.44,7.69]]},{"type":"LineString","coordinates":[[-8.44,7.69],[-8.44,7.69],[-8.72,7.71],[-8.93,7.31],[-9.21,7.31],[-9.4,7.53],[-9.34,7.93],[-9.76,8.54],[-10.02,8.43],[-10.23,8.41]]},{"type":"LineString","coordinates":[[-10.23,8.41],[-10.23,8.41],[-10.51,8.35],[-10.49,8.72],[-10.65,8.98],[-10.62,9.27],[-10.84,9.69],[-11.12,10.05],[-11.92,10.05],[-12.15,9.86],[-12.43,9.84],[-12.6,9.62],[-12.71,9.34],[-13.25,8.9]]},{"type":"LineString","coordinates":[[0.37,14.93],[-0.27,14.92],[-0.52,15.12],[-1.07,14.97],[-2.0,14.56],[-2.19,14.25],[-2.97,13.8],[-3.1,13.54],[-3.52,13.34],[-4.01,13.47],[-4.28,13.23],[-4.43,12.54],[-5.22,11.71],[-5.2,11.38],[-5.47,10.95],[-5.4,10.37],[-5.4,10.37]]},{"type":"LineString","coordinates":[[-5.4,10.37],[-5.82,10.22],[-6.05,10.1],[-6.21,10.52],[-6.49,10.41],[-6.67,10.43],[-6.85,10.14],[-7.62,10.15],[-7.9,10.3],[-8.03,10.21]]},{"type":"LineString","coordinates":[[2.15,11.94],[1.94,11.64],[1.45,11.55],[1.24,11.11],[0.9,11.0],[0.9,11.0]]},{"type":"LineString","coordinates":[[0.9,11.0],[0.02,11.02]]},{"type":"LineString","coordinates":[[-2.83,9.64],[-3.51,9.9],[-3.98,9.86],[-4.33,9.61],[-4.78,9.82],[-4.95,10.15],[-5.4,10.37]]},{"type":"LineString","coordinates":[[0.02,11.02],[0.02,11.02],[-0.44,11.1],[-0.76,10.94],[-1.2,11.01],[-2.94,10.96],[-2.96,10.4],[-2.83,9.64],[-2.83,9.64]]},{"type":"LineString","coordinates":[[3.61,11.66],[3.57,11.33],[3.8,10.73],[3.6,10.33],[3.71,10.06],[3.22,9.44],[2.91,9.14],[2.72,8.51],[2.75,7.87],[2.69,6.26]]},{"type":"LineString","coordinates":[[16.01,2.27],[15.94,1.73],[15.15,1.96],[14.34,2.23],[13.08,2.27],[13.08,2.27]]},{"type":"LineString","coordinates":[[13.08,2.27],[12.95,2.32],[12.36,2.19],[11.75,2.33],[11.28,2.26],[11.28,2.26]]},{"type":"LineString","coordinates":[[11.28,2.26],[9.65,2.28]]},{"type":"LineString","coordinates":[[23.81,8.67],[24.57,8.23],[25.11,7.83],[25.12,7.5],[25.8,6.98],[26.21,6.55],[26.47,5.95],[27.21,5.55],[27.37,5.23],[27.37,5.23]]},{"type":"LineString","coordinates":[[27.37,5.23],[27.04,5.13],[26.4,5.15],[25.65,5.26],[25.28,5.17],[25.13,4.93],[24.81,4.9],[24.41,5.11],[23.3,4.61],[22.84,4.71],[22.7,4.63],[22.41,4.03],[21.66,4.22],[20.93,4.32],[20.29,4.69],[19.47,5.03],[18.93,4.71],[18.54,4.2],[18.45,3.5]]},{"type":"LineString","coordinates":[[36.43,14.42],[36.27,13.56],[35.86,12.58],[35.26,12.08],[34.83,11.32],[34.73,10.91],[34.26,10.63],[33.96,9.58],[33.96,9.58]]},{"type":"LineString","coordinates":[[33.96,9.58],[33.97,8.68],[33.83,8.38],[33.29,8.35],[32.95,7.78],[33.57,7.71],[34.08,7.23],[34.25,6.83],[34.71,6.59],[35.3,5.51]]},{"type":"LineString","coordinates":[[42.35,12.54],[42.78,12.46],[43.08,12.7]]},{"type":"LineString","coordinates":[[42.78,10.93],[42.55,11.11],[42.31,11.03],[41.76,11.05],[41.74,11.36],[41.66,11.63],[42,12.1],[42.35,12.54]]},{"type":"LineString","coordinates":[[33.9,-0.95],[33.9,-0.95],[33.89,0.11],[34.18,0.52],[34.67,1.18],[35.04,1.91],[34.6,3.05],[34.48,3.56],[34.01,4.25]]},{"type":"LineString","coordinates":[[30.83,3.51],[29.95,4.17],[29.72,4.6],[29.16,4.39],[28.7,4.46],[28.43,4.29],[27.98,4.41],[27.37,5.23]]},{"type":"LineString","coordinates":[[18.45,3.5],[18.45,3.5],[18.39,2.9],[18.09,2.37],[17.9,1.74],[17.77,0.86],[17.83,0.29],[17.66,-0.06],[17.64,-0.42],[17.52,-0.74],[16.87,-1.23],[16.41,-1.74],[15.97,-2.71],[16.01,-3.54],[15.75,-3.86],[15.17,-4.34],[14.58,-4.97],[14.21,-4.79],[14.14,-4.51],[13.6,-4.5],[13.26,-4.88],[13.0,-4.78]]},{"type":"LineString","coordinates":[[13.0,-4.78],[13.0,-4.78],[12.63,-4.99],[12.47,-5.25],[12.44,-5.68],[12.18,-5.79]]},{"type":"LineString","coordinates":[[29.58,-1.34],[29.29,-1.62],[29.25,-2.22],[29.12,-2.29],[29.02,-2.84]]},{"type":"LineString","coordinates":[[29.02,-2.84],[29.28,-3.29],[29.34,-4.5]]},{"type":"LineString","coordinates":[[29.34,-4.5],[29.52,-5.42],[29.42,-5.94],[29.62,-6.52],[30.2,-7.08],[30.74,-8.34]]},{"type":"LineString","coordinates":[[30.74,-8.34],[30.35,-8.24],[29.0,-8.41],[28.73,-8.53],[28.45,-9.16],[28.67,-9.61],[28.5,-10.79],[28.37,-11.79],[28.64,-11.97],[29.34,-12.36],[29.62,-12.18],[29.7,-13.26],[28.93,-13.25],[28.52,-12.7],[28.16,-12.27],[27.39,-12.13],[27.16,-11.61],[26.55,-11.92],[25.75,-11.78],[25.42,-11.33],[24.78,-11.24],[24.31,-11.26],[24.26,-10.95],[23.91,-10.93]]},{"type":"LineString","coordinates":[[23.91,-10.93],[23.46,-10.87],[22.84,-11.02],[22.4,-10.99],[22.16,-11.08],[22.21,-9.89],[21.88,-9.52],[21.8,-8.91],[21.95,-8.31],[21.75,-7.92],[21.73,-7.29],[20.51,-7.3],[20.6,-6.94],[20.09,-6.94],[20.04,-7.12],[19.42,-7.16],[19.17,-7.74],[19.02,-7.99],[18.46,-7.85],[18.13,-7.99],[17.47,-8.07],[17.09,-7.55],[16.86,-7.22],[16.57,-6.62],[16.33,-5.88],[13.38,-5.86],[13.02,-5.98],[12.74,-5.97],[12.32,-6.1]]},{"type":"LineString","coordinates":[[30.47,-2.41],[30.47,-2.41],[29.94,-2.35],[29.63,-2.92],[29.02,-2.84]]},{"type":"LineString","coordinates":[[30.42,-1.13],[30.77,-1.01],[31.87,-1.03],[33.9,-0.95]]},{"type":"LineString","coordinates":[[32.76,-9.23],[32.19,-8.93],[31.56,-8.76],[31.16,-8.59],[30.74,-8.34]]},{"type":"LineString","coordinates":[[33.21,-13.97],[30.18,-14.8],[30.27,-15.51]]},{"type":"LineString","coordinates":[[30.27,-15.51],[30.27,-15.51],[29.52,-15.64],[28.95,-16.04],[28.83,-16.39],[28.47,-16.47],[27.6,-17.29],[27.04,-17.94],[26.71,-17.96],[26.38,-17.85],[25.26,-17.74],[25.08,-17.66]]},{"type":"LineString","coordinates":[[23.22,-17.52],[21.38,-17.93],[18.96,-17.79],[18.26,-17.31],[14.21,-17.35],[14.06,-17.42],[13.46,-16.97],[12.81,-16.94],[12.22,-17.11],[11.73,-17.3]]},{"type":"LineString","coordinates":[[24.93,-17.72],[24.52,-17.89],[24.22,-17.89],[23.58,-18.28],[23.2,-17.87],[21.66,-18.22],[20.91,-18.25],[20.88,-21.81],[19.9,-21.85],[19.9,-24.77],[19.9,-24.77]]},{"type":"LineString","coordinates":[[29.43,-22.09],[28.02,-22.83],[27.12,-23.57],[26.79,-24.24],[26.49,-24.62],[25.94,-24.7],[25.77,-25.17],[25.66,-25.49],[25.03,-25.72],[24.21,-25.67],[23.73,-25.39],[23.31,-25.27],[22.82,-25.5],[22.58,-25.98],[22.11,-26.28],[21.61,-26.73],[20.89,-26.83],[20.67,-26.48],[20.76,-25.87],[20.17,-24.92],[19.9,-24.77]]},{"type":"LineString","coordinates":[[31.19,-22.25],[31.19,-22.25],[30.66,-22.15],[30.32,-22.27],[29.84,-22.1],[29.43,-22.09]]},{"type":"LineString","coordinates":[[32.07,-26.73],[32.07,-26.73],[31.99,-26.29],[31.84,-25.84]]},{"type":"LineString","coordinates":[[31.84,-25.84],[31.84,-25.84],[31.75,-25.48],[31.93,-24.37],[31.67,-23.66],[31.19,-22.25]]},{"type":"LineString","coordinates":[[-89.14,17.81],[-89.14,17.81],[-89.15,17.96],[-89.03,18.0],[-88.85,17.88],[-88.49,18.49],[-88.3,18.5]]},{"type":"LineString","coordinates":[[-89.35,14.42],[-89.15,14.68],[-89.23,14.87],[-89.15,15.07],[-88.68,15.35],[-88.23,15.73]]},{"type":"LineString","coordinates":[[-66.88,1.25],[-67.07,1.13],[-67.26,1.72],[-67.54,2.04],[-67.87,1.69],[-69.82,1.71],[-69.8,1.09],[-69.22,0.99],[-69.25,0.6],[-69.45,0.71],[-70.02,0.54],[-70.02,-0.19],[-69.58,-0.55],[-69.42,-1.12],[-69.44,-1.56],[-69.89,-4.3],[-69.89,-4.3]]},{"type":"LineString","coordinates":[[-69.89,-4.3],[-70.39,-3.77],[-70.69,-3.74],[-70.05,-2.73],[-70.81,-2.26],[-71.41,-2.34],[-71.77,-2.17],[-72.33,-2.43],[-73.07,-2.31],[-73.66,-1.26],[-74.12,-1.0],[-74.44,-0.53],[-75.11,-0.06],[-75.37,-0.15],[-75.37,-0.15]]},{"type":"LineString","coordinates":[[-75.37,-0.15],[-75.8,0.08],[-76.29,0.42],[-76.58,0.26],[-77.42,0.4],[-77.67,0.83],[-77.86,0.81],[-78.86,1.38]]},{"type":"LineString","coordinates":[[-67.11,-22.74],[-67.11,-22.74],[-67.83,-22.87],[-68.22,-21.49],[-68.76,-20.37],[-68.44,-19.41],[-68.97,-18.98],[-69.1,-18.26],[-69.59,-17.58]]},{"type":"LineString","coordinates":[[-58.17,-20.18],[-58.18,-19.87],[-59.12,-19.36],[-60.04,-19.34],[-61.79,-19.63],[-62.27,-20.51],[-62.29,-21.05],[-62.69,-22.25]]},{"type":"LineString","coordinates":[[-54.63,-25.74],[-54.63,-25.74],[-54.13,-25.55],[-53.63,-26.12],[-53.65,-26.92],[-54.49,-27.47],[-55.16,-27.88],[-56.29,-28.85],[-57.63,-30.22]]},{"type":"LineString","coordinates":[[-57.63,-30.22],[-57.63,-30.22],[-56.98,-30.11],[-55.97,-30.88],[-55.6,-30.85],[-54.57,-31.49],[-53.79,-32.05],[-53.21,-32.73],[-53.65,-33.2],[-53.37,-33.77]]},{"type":"LineString","coordinates":[[-69.59,-17.58],[-68.96,-16.5],[-69.39,-15.66],[-69.16,-15.32],[-69.34,-14.95],[-68.95,-14.45],[-68.93,-13.6],[-68.88,-12.9],[-68.67,-12.56],[-69.53,-10.95]]},{"type":"LineString","coordinates":[[13.32,13.56],[13.32,13.56],[14.0,12.46],[14.18,12.48],[14.18,12.48]]},{"type":"LineString","coordinates":[[6.19,49.46],[5.9,49.44],[5.67,49.53]]},{"type":"MultiLineString","coordinates":[[[46.14,38.74],[45.46,38.87],[44.95,39.34],[44.79,39.71]],[[46.51,38.77],[47.69,39.51],[48.06,39.58],[48.36,39.29],[48.01,38.79],[48.63,38.27],[48.88,38.32]]]},{"type":"LineString","coordinates":[[-13.7,12.59],[-13.7,12.59],[-15.55,12.63],[-15.82,12.52],[-16.15,12.55],[-16.68,12.38]]},{"type":"LineString","coordinates":[[19.9,-24.77],[19.89,-28.46],[19.0,-28.97],[18.46,-29.05],[17.84,-28.86],[17.39,-28.78],[17.22,-28.36],[16.82,-28.08],[16.34,-28.58]]},{"type":"LineString","coordinates":[[-62.69,-22.25],[-62.69,-22.25],[-62.85,-22.03],[-63.99,-21.99],[-64.38,-22.8],[-64.96,-22.08],[-66.27,-21.83],[-67.11,-22.74]]},{"type":"MultiLineString","coordinates":[[[-69.53,-10.95],[-69.53,-10.95],[-68.79,-11.04],[-68.27,-11.01],[-68.05,-10.71],[-67.17,-10.31],[-66.65,-9.93],[-65.34,-9.76],[-65.44,-10.51],[-65.32,-10.9],[-65.4,-11.57],[-64.32,-12.46],[-63.2,-12.63],[-62.8,-13.0],[-62.13,-13.2],[-61.71,-13.49],[-61.08,-13.48],[-60.5,-13.78],[-60.46,-14.35],[-60.26,-14.65],[-60.25,-15.08],[-60.54,-15.09],[-60.16,-16.26],[-58.24,-16.3],[-58.39,-16.88],[-58.28,-17.27],[-57.73,-17.55],[-57.5,-18.17],[-57.68,-18.96],[-57.95,-19.4],[-57.85,-19.97],[-57.85,-19.97],[-58.17,-20.18]],[[-57.85,-19.97],[-58.17,-20.18]]]},{"type":"LineString","coordinates":[[75.9,36.67],[76.19,35.9],[76.65,35.79]]},{"type":"LineString","coordinates":[[79.21,32.99],[79.21,32.99],[79.18,32.48],[79.18,32.48]]},{"type":"LineString","coordinates":[[79.18,32.48],[78.46,32.62],[78.55,32.27]]},{"type":"LineString","coordinates":[[78.55,32.27],[78.65,31.85]]},{"type":"LineString","coordinates":[[78.65,31.85],[78.74,31.52],[78.88,31.43]]},{"type":"LineString","coordinates":[[78.88,31.43],[79.37,31.11]]},{"type":"LineString","coordinates":[[79.37,31.11],[79.72,30.88],[79.81,30.84]]},{"type":"LineString","coordinates":[[79.81,30.84],[80.26,30.61]]},{"type":"LineString","coordinates":[[80.26,30.61],[81.11,30.18],[81.11,30.18]]},{"type":"LineString","coordinates":[[36.7,45.61],[36.58,45.2],[36.64,44.95]]},{"type":"LineString","coordinates":[[87.36,49.22],[87.75,49.3],[88.01,48.6],[88.85,48.07],[90.28,47.69],[90.97,46.89],[90.59,45.72],[90.95,45.29],[92.13,45.12],[93.48,44.98],[94.69,44.35],[95.31,44.24],[95.76,43.32],[96.35,42.73],[97.45,42.75],[99.52,42.52],[100.85,42.66],[101.83,42.51],[103.31,41.91],[104.52,41.91],[104.97,41.6],[106.13,42.13],[107.74,42.48],[109.24,42.52],[110.41,42.87],[111.13,43.41],[111.83,43.74],[111.67,44.07],[111.35,44.46],[111.87,45.1],[112.44,45.01],[113.46,44.81],[114.46,45.34],[115.99,45.73],[116.72,46.39],[117.42,46.67],[118.87,46.81],[119.66,46.69],[119.77,47.05],[118.87,47.75],[118.06,48.07],[117.3,47.7],[116.31,47.85],[115.74,47.73],[115.49,48.14],[116.19,49.13],[116.68,49.89],[116.68,49.89]]},{"type":"LineString","coordinates":[[116.68,49.89],[116.68,49.89],[117.88,49.51],[119.29,50.14],[119.28,50.58],[120.18,51.64],[120.74,51.96],[120.73,52.52],[120.18,52.75],[121.0,53.25],[122.25,53.43],[123.57,53.46],[125.07,53.16],[125.95,52.79],[126.56,51.78],[126.94,51.35],[127.29,50.74],[127.66,49.76],[129.4,49.44],[130.58,48.73],[130.99,47.79],[132.51,47.79],[133.37,48.18],[135.03,48.48],[134.5,47.58],[134.11,47.21],[133.77,46.12],[133.1,45.14],[131.88,45.32],[131.03,44.97],[131.29,44.11],[131.14,42.93],[130.63,42.9],[130.64,42.4]]},{"type":"LineString","coordinates":[[35.55,33.26],[35.46,33.09],[35.13,33.09]]},{"type":"LineString","coordinates":[[75.14,34.6],[74.24,34.75],[73.75,34.32],[74.1,33.44],[74.45,32.76]]},{"type":"LineString","coordinates":[[77.29,35.05],[76.87,34.65],[75.76,34.5],[75.14,34.6]]}]} \ No newline at end of file diff --git a/app/src/main/assets/world_cities.geojson b/app/src/main/assets/world_cities.geojson deleted file mode 100644 index 435f8654..00000000 --- a/app/src/main/assets/world_cities.geojson +++ /dev/null @@ -1 +0,0 @@ -{"cities":[{"n":"Bombo","r":10,"cap":0,"mega":0,"pop":75000,"lon":32.533,"lat":0.583},{"n":"Fort Portal","r":10,"cap":0,"mega":0,"pop":42670,"lon":30.275,"lat":0.671},{"n":"Potenza","r":10,"cap":0,"mega":0,"pop":69060,"lon":15.799,"lat":40.642},{"n":"Campobasso","r":10,"cap":0,"mega":0,"pop":50762,"lon":14.656,"lat":41.563},{"n":"Aosta","r":10,"cap":0,"mega":0,"pop":34062,"lon":7.315,"lat":45.737},{"n":"Mariehamn","r":10,"cap":0,"mega":0,"pop":10682,"lon":19.949,"lat":60.097},{"n":"Ramallah","r":10,"cap":0,"mega":0,"pop":24599,"lon":35.206,"lat":31.903},{"n":"Vatican City","r":8,"cap":1,"mega":0,"pop":832,"lon":12.453,"lat":41.903},{"n":"Poitier","r":8,"cap":0,"mega":0,"pop":85960,"lon":0.333,"lat":46.583},{"n":"Clermont-Ferrand","r":8,"cap":0,"mega":0,"pop":233050,"lon":3.08,"lat":45.78},{"n":"Besancon","r":8,"cap":0,"mega":0,"pop":128426,"lon":6.03,"lat":47.23},{"n":"Artigas Base","r":8,"cap":0,"mega":0,"pop":60,"lon":-58.864,"lat":-62.174},{"n":"Chipata","r":7,"cap":0,"mega":0,"pop":85963,"lon":32.64,"lat":-13.63},{"n":"Jinja","r":7,"cap":0,"mega":0,"pop":301619,"lon":33.2,"lat":0.44},{"n":"Arua","r":7,"cap":0,"mega":0,"pop":250000,"lon":30.9,"lat":3.02},{"n":"Mbale","r":7,"cap":0,"mega":0,"pop":402368,"lon":34.17,"lat":1.09},{"n":"Moroto","r":7,"cap":0,"mega":0,"pop":371,"lon":34.64,"lat":2.54},{"n":"Masaka","r":7,"cap":0,"mega":0,"pop":65373,"lon":31.73,"lat":-0.33},{"n":"Mbarara","r":7,"cap":0,"mega":0,"pop":83700,"lon":30.65,"lat":-0.6},{"n":"Otjiwarongo","r":7,"cap":0,"mega":0,"pop":24815,"lon":16.64,"lat":-20.46},{"n":"Bologna","r":7,"cap":0,"mega":0,"pop":488172,"lon":11.34,"lat":44.5},{"n":"Cagliari","r":7,"cap":0,"mega":0,"pop":291511,"lon":9.104,"lat":39.222},{"n":"Catanzaro","r":7,"cap":0,"mega":0,"pop":95251,"lon":16.6,"lat":38.9},{"n":"Bari","r":7,"cap":0,"mega":0,"pop":500577,"lon":16.873,"lat":41.114},{"n":"L'Aquila","r":7,"cap":0,"mega":0,"pop":68503,"lon":13.39,"lat":42.35},{"n":"Ancona","r":7,"cap":0,"mega":0,"pop":100507,"lon":13.5,"lat":43.6},{"n":"Perugia","r":7,"cap":0,"mega":0,"pop":149125,"lon":12.39,"lat":43.11},{"n":"Trieste","r":7,"cap":0,"mega":0,"pop":216035,"lon":13.8,"lat":45.65},{"n":"Trento","r":7,"cap":0,"mega":0,"pop":107808,"lon":11.12,"lat":46.08},{"n":"Fort-de-France","r":7,"cap":0,"mega":0,"pop":253995,"lon":-61.08,"lat":14.61},{"n":"Gifu","r":7,"cap":0,"mega":0,"pop":412895,"lon":136.763,"lat":35.423},{"n":"Saint-Laurent-du-Maroni","r":7,"cap":0,"mega":0,"pop":24287,"lon":-54.033,"lat":5.498},{"n":"Caen","r":7,"cap":0,"mega":0,"pop":190099,"lon":-0.35,"lat":49.184},{"n":"Nantes","r":7,"cap":0,"mega":0,"pop":438537,"lon":-1.59,"lat":47.21},{"n":"Ajaccio","r":7,"cap":0,"mega":0,"pop":54364,"lon":8.728,"lat":41.927},{"n":"Montpellier","r":7,"cap":0,"mega":0,"pop":327254,"lon":3.87,"lat":43.61},{"n":"Dijon","r":7,"cap":0,"mega":0,"pop":169946,"lon":5.03,"lat":47.33},{"n":"Orleans","r":7,"cap":0,"mega":0,"pop":217301,"lon":1.9,"lat":47.9},{"n":"Rouen","r":7,"cap":0,"mega":0,"pop":532559,"lon":1.08,"lat":49.43},{"n":"Reims","r":7,"cap":0,"mega":0,"pop":196565,"lon":4.03,"lat":49.25},{"n":"Amiens","r":7,"cap":0,"mega":0,"pop":143086,"lon":2.3,"lat":49.9},{"n":"Nancy","r":7,"cap":0,"mega":0,"pop":268976,"lon":6.2,"lat":48.684},{"n":"Basse-terre","r":7,"cap":0,"mega":0,"pop":307,"lon":-61.705,"lat":16.01},{"n":"Novi Sad","r":7,"cap":0,"mega":0,"pop":225457,"lon":19.85,"lat":45.25},{"n":"Banja Luka","r":7,"cap":0,"mega":0,"pop":221738,"lon":17.18,"lat":44.78},{"n":"George Town","r":7,"cap":0,"mega":0,"pop":4268,"lon":-81.372,"lat":19.295},{"n":"Grand Turk","r":7,"cap":0,"mega":0,"pop":5801,"lon":-71.136,"lat":21.466},{"n":"Douglas","r":7,"cap":0,"mega":0,"pop":35854,"lon":-4.48,"lat":54.149},{"n":"San Marino","r":7,"cap":1,"mega":0,"pop":29579,"lon":12.442,"lat":43.936},{"n":"Willemstad","r":7,"cap":0,"mega":0,"pop":146813,"lon":-68.872,"lat":12.112},{"n":"Oranjestad","r":7,"cap":0,"mega":0,"pop":68775,"lon":-70.026,"lat":12.522},{"n":"Vaduz","r":7,"cap":1,"mega":0,"pop":36281,"lon":9.517,"lat":47.134},{"n":"Capitan Arturo Prat Station","r":7,"cap":0,"mega":0,"pop":41,"lon":-59.683,"lat":-62.5},{"n":"Marambio Base","r":7,"cap":0,"mega":0,"pop":150,"lon":-56.65,"lat":-64.233},{"n":"Zucchelli Station","r":7,"cap":0,"mega":0,"pop":80,"lon":164.219,"lat":-74.619},{"n":"Rothera Station","r":7,"cap":0,"mega":0,"pop":130,"lon":-68.124,"lat":-67.565},{"n":"Palmer Station","r":7,"cap":0,"mega":0,"pop":46,"lon":-64.047,"lat":-64.762},{"n":"Base Presidente Montalva","r":7,"cap":0,"mega":0,"pop":150,"lon":-58.908,"lat":-62.183},{"n":"Carlini Base","r":7,"cap":0,"mega":0,"pop":60,"lon":-58.65,"lat":-62.224},{"n":"King Sejong Station","r":7,"cap":0,"mega":0,"pop":90,"lon":-58.772,"lat":-62.225},{"n":"Great Wall Station","r":7,"cap":0,"mega":0,"pop":40,"lon":-58.967,"lat":-62.216},{"n":"Escudero Base","r":7,"cap":0,"mega":0,"pop":20,"lon":-58.953,"lat":-62.195},{"n":"Elephant Island","r":7,"cap":0,"mega":0,"pop":6,"lon":-58.0,"lat":-62.0},{"n":"Scott Base","r":7,"cap":0,"mega":0,"pop":85,"lon":166.749,"lat":-77.847},{"n":"McMurdo Station","r":7,"cap":0,"mega":0,"pop":1000,"lon":166.869,"lat":-77.732},{"n":"Zhongshan Station","r":7,"cap":0,"mega":0,"pop":60,"lon":76.339,"lat":-69.434},{"n":"Vostok","r":7,"cap":0,"mega":0,"pop":25,"lon":106.8,"lat":-78.466},{"n":"Peter I Island","r":7,"cap":0,"mega":0,"pop":1,"lon":-90.584,"lat":-68.795},{"n":"Mirny Station","r":7,"cap":0,"mega":0,"pop":169,"lon":93.006,"lat":-66.576},{"n":"Mawson Station","r":7,"cap":0,"mega":0,"pop":60,"lon":62.875,"lat":-67.615},{"n":"Davis Station","r":7,"cap":0,"mega":0,"pop":70,"lon":78.141,"lat":-68.778},{"n":"Concordia Research Station","r":7,"cap":0,"mega":0,"pop":30,"lon":124.167,"lat":-74.666},{"n":"Casey Station","r":7,"cap":0,"mega":0,"pop":200,"lon":110.536,"lat":-66.286},{"n":"AmundseniScott South Pole Station","r":7,"cap":0,"mega":0,"pop":200,"lon":176.994,"lat":-90},{"n":"Wasa Station","r":7,"cap":0,"mega":0,"pop":10,"lon":-13.417,"lat":-73.05},{"n":"Troll Station","r":7,"cap":0,"mega":0,"pop":44,"lon":2.533,"lat":-72.016},{"n":"Svea Station","r":7,"cap":0,"mega":0,"pop":10,"lon":-11.217,"lat":-74.583},{"n":"Novolazarevskaya Station","r":7,"cap":0,"mega":0,"pop":70,"lon":-11.85,"lat":-71.301},{"n":"Neumayer Station III","r":7,"cap":0,"mega":0,"pop":40,"lon":-7.838,"lat":-70.89},{"n":"Maitri Station","r":7,"cap":0,"mega":0,"pop":65,"lon":11.73,"lat":-70.782},{"n":"Halley Station","r":7,"cap":0,"mega":0,"pop":70,"lon":-26.474,"lat":-76.092},{"n":"Belgrano II Base","r":7,"cap":0,"mega":0,"pop":100,"lon":-34.617,"lat":-77.866},{"n":"Sobral Base","r":7,"cap":0,"mega":0,"pop":40,"lon":-40.502,"lat":-81.179},{"n":"Aboa Station","r":7,"cap":0,"mega":0,"pop":18,"lon":-13.417,"lat":-75.05},{"n":"San Martin Base","r":7,"cap":0,"mega":0,"pop":20,"lon":-67.1,"lat":-68.116},{"n":"Gen. O'Higgins Base","r":7,"cap":0,"mega":0,"pop":44,"lon":-57.89,"lat":-63.328},{"n":"Esperanza Station","r":7,"cap":0,"mega":0,"pop":250,"lon":-57.026,"lat":-63.375},{"n":"Orcadas Station","r":7,"cap":0,"mega":0,"pop":45,"lon":-44.733,"lat":-60.733},{"n":"Signy Research Station","r":7,"cap":0,"mega":0,"pop":8,"lon":-45.601,"lat":-60.631},{"n":"Dumont d'Urville Station","r":7,"cap":0,"mega":0,"pop":120,"lon":139.964,"lat":-66.775},{"n":"Showa Station","r":7,"cap":0,"mega":0,"pop":60,"lon":39.746,"lat":-69.03},{"n":"Gibraltar","r":6,"cap":0,"mega":0,"pop":187083,"lon":-5.346,"lat":36.128},{"n":"Lobamba","r":6,"cap":0,"mega":0,"pop":9782,"lon":31.2,"lat":-26.467},{"n":"Edinburgh","r":6,"cap":0,"mega":0,"pop":504966,"lon":-3.219,"lat":55.948},{"n":"Cardiff","r":6,"cap":0,"mega":0,"pop":861400,"lon":-3.168,"lat":51.483},{"n":"Longyearbyen","r":6,"cap":0,"mega":0,"pop":1232,"lon":15.621,"lat":78.221},{"n":"Luxembourg","r":6,"cap":1,"mega":0,"pop":107260,"lon":6.13,"lat":49.612},{"n":"Turin","r":6,"cap":0,"mega":1,"pop":1652000,"lon":7.668,"lat":45.072},{"n":"Noumea","r":6,"cap":0,"mega":0,"pop":93060,"lon":166.444,"lat":-22.263},{"n":"Matsuyama","r":6,"cap":0,"mega":0,"pop":533541,"lon":132.766,"lat":33.846},{"n":"Rennes","r":6,"cap":0,"mega":0,"pop":209375,"lon":-1.67,"lat":48.1},{"n":"Toulouse","r":6,"cap":0,"mega":1,"pop":847000,"lon":1.448,"lat":43.622},{"n":"Limoges","r":6,"cap":0,"mega":0,"pop":152199,"lon":1.25,"lat":45.83},{"n":"Lille","r":6,"cap":0,"mega":1,"pop":1044000,"lon":3.078,"lat":50.652},{"n":"Strasbourg","r":6,"cap":0,"mega":0,"pop":439972,"lon":7.75,"lat":48.58},{"n":"Batumi","r":6,"cap":0,"mega":0,"pop":155542,"lon":41.63,"lat":41.6},{"n":"Funchal","r":6,"cap":0,"mega":0,"pop":204767,"lon":-16.88,"lat":32.65},{"n":"El Fasher","r":6,"cap":0,"mega":0,"pop":252609,"lon":25.35,"lat":13.63},{"n":"Keetmanshoop","r":6,"cap":0,"mega":0,"pop":18039,"lon":18.13,"lat":-26.574},{"n":"Swakopmund","r":6,"cap":0,"mega":0,"pop":29491,"lon":14.535,"lat":-22.669},{"n":"Genoa","r":6,"cap":0,"mega":0,"pop":647497,"lon":8.93,"lat":44.41},{"n":"Sukhumi","r":6,"cap":0,"mega":0,"pop":81546,"lon":41.02,"lat":43.02},{"n":"Palikir","r":6,"cap":1,"mega":0,"pop":4645,"lon":158.15,"lat":6.917},{"n":"Majuro","r":6,"cap":1,"mega":0,"pop":25400,"lon":171.38,"lat":7.103},{"n":"Agana","r":6,"cap":0,"mega":0,"pop":122411,"lon":144.75,"lat":13.47},{"n":"Funafuti","r":6,"cap":1,"mega":0,"pop":4749,"lon":179.217,"lat":-8.517},{"n":"Melekeok","r":6,"cap":1,"mega":0,"pop":7026,"lon":134.627,"lat":7.487},{"n":"Bir Lehlou","r":6,"cap":0,"mega":0,"pop":500,"lon":-9.653,"lat":26.119},{"n":"Monaco","r":6,"cap":1,"mega":0,"pop":36371,"lon":7.407,"lat":43.74},{"n":"Tarawa","r":6,"cap":1,"mega":0,"pop":28802,"lon":173.018,"lat":1.338},{"n":"Moroni","r":6,"cap":1,"mega":0,"pop":128698,"lon":43.24,"lat":-11.704},{"n":"Macau","r":6,"cap":0,"mega":0,"pop":568700,"lon":113.544,"lat":22.189},{"n":"Andorra","r":6,"cap":1,"mega":0,"pop":53998,"lon":1.527,"lat":42.511},{"n":"San Bernardino","r":4,"cap":0,"mega":1,"pop":1745000,"lon":-117.302,"lat":34.122},{"n":"Bridgeport","r":4,"cap":0,"mega":1,"pop":1018000,"lon":-73.202,"lat":41.182},{"n":"Rochester","r":4,"cap":0,"mega":1,"pop":755000,"lon":-77.622,"lat":43.172},{"n":"Manchester","r":4,"cap":0,"mega":1,"pop":2230000,"lon":-2.249,"lat":53.475},{"n":"Gujranwala","r":4,"cap":0,"mega":1,"pop":1513000,"lon":74.183,"lat":32.162},{"n":"Incheon","r":4,"cap":0,"mega":1,"pop":2550000,"lon":126.64,"lat":37.478},{"n":"Benin City","r":4,"cap":0,"mega":1,"pop":1190000,"lon":5.618,"lat":6.342},{"n":"Xiamen","r":4,"cap":0,"mega":1,"pop":2519000,"lon":118.078,"lat":24.452},{"n":"Nanchong","r":4,"cap":0,"mega":1,"pop":2174000,"lon":106.128,"lat":30.782},{"n":"Neijiang","r":4,"cap":0,"mega":1,"pop":1466000,"lon":105.048,"lat":29.582},{"n":"Nanyang","r":4,"cap":0,"mega":1,"pop":1944000,"lon":112.528,"lat":33.002},{"n":"Jinxi","r":4,"cap":0,"mega":1,"pop":2426000,"lon":120.828,"lat":40.752},{"n":"Yantai","r":4,"cap":0,"mega":1,"pop":2116000,"lon":121.398,"lat":37.532},{"n":"Zaozhuang","r":4,"cap":0,"mega":1,"pop":2145000,"lon":117.568,"lat":34.882},{"n":"Suzhou","r":4,"cap":0,"mega":1,"pop":1650000,"lon":120.618,"lat":31.302},{"n":"Xuzhou","r":4,"cap":0,"mega":1,"pop":2091000,"lon":117.178,"lat":34.282},{"n":"Wuxi","r":4,"cap":0,"mega":1,"pop":1749000,"lon":120.298,"lat":31.582},{"n":"Jilin","r":4,"cap":0,"mega":1,"pop":2396000,"lon":126.548,"lat":43.852},{"n":"Chandigarh","r":4,"cap":0,"mega":1,"pop":979000,"lon":76.778,"lat":30.722},{"n":"Jammu","r":4,"cap":0,"mega":1,"pop":791000,"lon":74.845,"lat":32.714},{"n":"Sholapur","r":4,"cap":0,"mega":1,"pop":1057000,"lon":75.898,"lat":17.672},{"n":"Aurangabad","r":4,"cap":0,"mega":1,"pop":1113000,"lon":75.329,"lat":19.862},{"n":"Nasik","r":4,"cap":0,"mega":1,"pop":1473000,"lon":73.778,"lat":20.002},{"n":"Dispur","r":4,"cap":0,"mega":0,"pop":16140,"lon":91.767,"lat":26.144},{"n":"Jullundur","r":4,"cap":0,"mega":1,"pop":855000,"lon":75.567,"lat":31.337},{"n":"Allahabad","r":4,"cap":0,"mega":1,"pop":1201000,"lon":81.838,"lat":25.457},{"n":"Moradabad","r":4,"cap":0,"mega":1,"pop":787000,"lon":78.755,"lat":28.844},{"n":"Ghaziabad","r":4,"cap":0,"mega":1,"pop":1341000,"lon":77.406,"lat":28.662},{"n":"Agra","r":4,"cap":0,"mega":1,"pop":1592000,"lon":78.013,"lat":27.172},{"n":"Aligarh","r":4,"cap":0,"mega":1,"pop":805000,"lon":78.06,"lat":27.894},{"n":"Meerut","r":4,"cap":0,"mega":1,"pop":1398000,"lon":77.698,"lat":29.002},{"n":"Dhanbad","r":4,"cap":0,"mega":1,"pop":1246000,"lon":86.418,"lat":23.802},{"n":"Gwalior","r":4,"cap":0,"mega":1,"pop":978000,"lon":78.169,"lat":26.2},{"n":"Vadodara","r":4,"cap":0,"mega":1,"pop":1756000,"lon":73.178,"lat":22.312},{"n":"Rajkot","r":4,"cap":0,"mega":1,"pop":1260000,"lon":70.798,"lat":22.312},{"n":"Durazno","r":4,"cap":0,"mega":0,"pop":34037,"lon":-56.51,"lat":-33.41},{"n":"International Falls","r":4,"cap":0,"mega":0,"pop":15240,"lon":-93.411,"lat":48.601},{"n":"St. Paul","r":4,"cap":0,"mega":0,"pop":734854,"lon":-93.085,"lat":44.944},{"n":"Billings","r":4,"cap":0,"mega":0,"pop":104552,"lon":-108.54,"lat":45.788},{"n":"Great Falls","r":4,"cap":0,"mega":0,"pop":66558,"lon":-111.3,"lat":47.5},{"n":"Missoula","r":4,"cap":0,"mega":0,"pop":72856,"lon":-113.993,"lat":46.872},{"n":"Minot","r":4,"cap":0,"mega":0,"pop":39439,"lon":-101.296,"lat":48.232},{"n":"Fargo","r":4,"cap":0,"mega":0,"pop":162842,"lon":-96.789,"lat":46.877},{"n":"Hilo","r":4,"cap":0,"mega":0,"pop":52391,"lon":-155.082,"lat":19.706},{"n":"Olympia","r":4,"cap":0,"mega":0,"pop":156984,"lon":-122.899,"lat":47.038},{"n":"Spokane","r":4,"cap":0,"mega":0,"pop":347705,"lon":-117.42,"lat":47.67},{"n":"Vancouver","r":4,"cap":0,"mega":0,"pop":525802,"lon":-122.64,"lat":45.63},{"n":"Flagstaff","r":4,"cap":0,"mega":0,"pop":63993,"lon":-111.651,"lat":35.198},{"n":"Tucson","r":4,"cap":0,"mega":1,"pop":823000,"lon":-110.892,"lat":32.207},{"n":"Santa Barbara","r":4,"cap":0,"mega":0,"pop":181632,"lon":-119.717,"lat":34.411},{"n":"Fresno","r":4,"cap":0,"mega":0,"pop":616353,"lon":-119.773,"lat":36.748},{"n":"Eureka","r":4,"cap":0,"mega":0,"pop":42398,"lon":-124.15,"lat":40.793},{"n":"Colorado Springs","r":4,"cap":0,"mega":0,"pop":493654,"lon":-104.792,"lat":38.863},{"n":"Reno","r":4,"cap":0,"mega":0,"pop":328585,"lon":-119.82,"lat":39.53},{"n":"Elko","r":4,"cap":0,"mega":0,"pop":19252,"lon":-115.762,"lat":40.833},{"n":"Albuquerque","r":4,"cap":0,"mega":0,"pop":898642,"lon":-106.641,"lat":35.105},{"n":"Salem","r":4,"cap":0,"mega":0,"pop":229010,"lon":-123.024,"lat":44.928},{"n":"Casper","r":4,"cap":0,"mega":0,"pop":60791,"lon":-106.323,"lat":42.847},{"n":"Topeka","r":4,"cap":0,"mega":0,"pop":132091,"lon":-95.67,"lat":39.05},{"n":"Kansas City","r":4,"cap":0,"mega":1,"pop":1469000,"lon":-94.606,"lat":39.109},{"n":"Tulsa","r":4,"cap":0,"mega":0,"pop":946962,"lon":-95.93,"lat":36.12},{"n":"Sioux Falls","r":4,"cap":0,"mega":0,"pop":155724,"lon":-96.73,"lat":43.55},{"n":"Shreveport","r":4,"cap":0,"mega":0,"pop":248053,"lon":-93.77,"lat":32.5},{"n":"Baton Rouge","r":4,"cap":0,"mega":0,"pop":422072,"lon":-91.14,"lat":30.458},{"n":"Ft. Worth","r":4,"cap":0,"mega":0,"pop":1440454,"lon":-97.34,"lat":32.74},{"n":"Corpus Christi","r":4,"cap":0,"mega":0,"pop":277454,"lon":-97.402,"lat":27.743},{"n":"Austin","r":4,"cap":0,"mega":1,"pop":1161000,"lon":-97.745,"lat":30.269},{"n":"Amarillo","r":4,"cap":0,"mega":0,"pop":181766,"lon":-101.83,"lat":35.23},{"n":"El Paso","r":4,"cap":0,"mega":1,"pop":753000,"lon":-106.512,"lat":31.782},{"n":"Laredo","r":4,"cap":0,"mega":0,"pop":434768,"lon":-99.507,"lat":27.506},{"n":"Merida","r":4,"cap":0,"mega":0,"pop":345489,"lon":-71.183,"lat":8.57},{"n":"Burlington","r":4,"cap":0,"mega":0,"pop":93807,"lon":-73.212,"lat":44.476},{"n":"Montgomery","r":4,"cap":0,"mega":0,"pop":198325,"lon":-86.279,"lat":32.362},{"n":"Tallahassee","r":4,"cap":0,"mega":0,"pop":221222,"lon":-84.28,"lat":30.45},{"n":"Orlando","r":4,"cap":0,"mega":1,"pop":1350000,"lon":-81.382,"lat":28.512},{"n":"Jacksonville","r":4,"cap":0,"mega":1,"pop":988000,"lon":-81.672,"lat":30.332},{"n":"Savannah","r":4,"cap":0,"mega":0,"pop":180187,"lon":-81.11,"lat":32.021},{"n":"Columbia","r":4,"cap":0,"mega":0,"pop":398093,"lon":-81.031,"lat":33.997},{"n":"Indianapolis","r":4,"cap":0,"mega":1,"pop":1436000,"lon":-86.172,"lat":39.752},{"n":"Wilmington","r":4,"cap":0,"mega":0,"pop":161560,"lon":-77.945,"lat":34.226},{"n":"Knoxville","r":4,"cap":0,"mega":0,"pop":655400,"lon":-83.92,"lat":35.97},{"n":"Richmond","r":4,"cap":0,"mega":1,"pop":912000,"lon":-77.452,"lat":37.552},{"n":"Charleston","r":4,"cap":0,"mega":0,"pop":123799,"lon":-81.633,"lat":38.35},{"n":"Baltimore","r":4,"cap":0,"mega":1,"pop":2255000,"lon":-76.615,"lat":39.282},{"n":"Syracuse","r":4,"cap":0,"mega":0,"pop":662577,"lon":-76.15,"lat":43.05},{"n":"Puerto Ayacucho","r":4,"cap":0,"mega":0,"pop":52526,"lon":-67.624,"lat":5.664},{"n":"Port-of-Spain","r":4,"cap":1,"mega":0,"pop":294934,"lon":-61.517,"lat":10.652},{"n":"Augusta","r":4,"cap":0,"mega":0,"pop":24042,"lon":-69.78,"lat":44.311},{"n":"Sault Ste. Marie","r":4,"cap":0,"mega":0,"pop":83805,"lon":-84.345,"lat":46.495},{"n":"Atakpame","r":4,"cap":0,"mega":0,"pop":80683,"lon":1.12,"lat":7.53},{"n":"Sousse","r":4,"cap":0,"mega":0,"pop":327004,"lon":10.625,"lat":35.83},{"n":"Taizz","r":4,"cap":0,"mega":1,"pop":751000,"lon":44.037,"lat":13.606},{"n":"Sitka","r":4,"cap":0,"mega":0,"pop":8931,"lon":-135.328,"lat":57.06},{"n":"Lvov","r":4,"cap":0,"mega":0,"pop":803880,"lon":24.03,"lat":49.835},{"n":"Odessa","r":4,"cap":0,"mega":1,"pop":991000,"lon":30.708,"lat":46.492},{"n":"Zhytomyr","r":4,"cap":0,"mega":0,"pop":282192,"lon":28.662,"lat":50.246},{"n":"Dnipro","r":4,"cap":0,"mega":1,"pop":1050000,"lon":34.998,"lat":48.482},{"n":"Donetsk","r":4,"cap":0,"mega":1,"pop":988000,"lon":37.828,"lat":48.002},{"n":"Kharkiv","r":4,"cap":0,"mega":1,"pop":1461000,"lon":36.248,"lat":50.002},{"n":"Turkmenbasy","r":4,"cap":0,"mega":0,"pop":68292,"lon":52.97,"lat":40.023},{"n":"Bukhara","r":4,"cap":0,"mega":0,"pop":319476,"lon":64.43,"lat":39.78},{"n":"Nukus","r":4,"cap":0,"mega":0,"pop":230006,"lon":59.615,"lat":42.47},{"n":"Turkmenabat","r":4,"cap":0,"mega":0,"pop":234817,"lon":63.58,"lat":39.11},{"n":"Mary","r":4,"cap":0,"mega":0,"pop":178708,"lon":61.833,"lat":37.6},{"n":"Andijon","r":4,"cap":0,"mega":0,"pop":650000,"lon":72.34,"lat":40.79},{"n":"Haiphong","r":4,"cap":0,"mega":1,"pop":1969000,"lon":106.678,"lat":20.832},{"n":"Da Nang","r":4,"cap":0,"mega":0,"pop":1000000,"lon":108.25,"lat":16.06},{"n":"Kabwe","r":4,"cap":0,"mega":0,"pop":188979,"lon":28.45,"lat":-14.44},{"n":"Mufulira","r":4,"cap":0,"mega":0,"pop":153624,"lon":28.26,"lat":-12.55},{"n":"Kitwe","r":4,"cap":0,"mega":0,"pop":404901,"lon":28.22,"lat":-12.81},{"n":"Livingstone","r":4,"cap":0,"mega":0,"pop":165480,"lon":25.86,"lat":-17.86},{"n":"Chitungwiza","r":4,"cap":0,"mega":0,"pop":340360,"lon":31.1,"lat":-18.0},{"n":"Douala","r":4,"cap":0,"mega":1,"pop":1906000,"lon":9.708,"lat":4.062},{"n":"Birmingham","r":4,"cap":0,"mega":1,"pop":2285000,"lon":-1.922,"lat":52.477},{"n":"Belfast","r":4,"cap":0,"mega":0,"pop":450406,"lon":-5.928,"lat":54.594},{"n":"Izmir","r":4,"cap":0,"mega":1,"pop":2587000,"lon":27.15,"lat":38.438},{"n":"Bursa","r":4,"cap":0,"mega":1,"pop":1492000,"lon":29.068,"lat":40.202},{"n":"Samsun","r":4,"cap":0,"mega":0,"pop":609339,"lon":36.344,"lat":41.28},{"n":"Konya","r":4,"cap":0,"mega":1,"pop":919000,"lon":32.473,"lat":37.877},{"n":"Adana","r":4,"cap":0,"mega":1,"pop":1293000,"lon":35.318,"lat":36.997},{"n":"Gulu","r":4,"cap":0,"mega":0,"pop":146858,"lon":32.28,"lat":2.78},{"n":"Kigali","r":4,"cap":1,"mega":1,"pop":860000,"lon":30.059,"lat":-1.952},{"n":"Cottica","r":4,"cap":0,"mega":0,"pop":29210,"lon":-54.233,"lat":3.85},{"n":"Cordoba","r":4,"cap":0,"mega":0,"pop":321376,"lon":-4.77,"lat":37.88},{"n":"Maradi","r":4,"cap":0,"mega":0,"pop":232555,"lon":7.096,"lat":13.492},{"n":"Tahoua","r":4,"cap":0,"mega":0,"pop":115956,"lon":5.26,"lat":14.9},{"n":"Constanta","r":4,"cap":0,"mega":0,"pop":303399,"lon":28.61,"lat":44.203},{"n":"Lulea","r":4,"cap":0,"mega":0,"pop":48638,"lon":22.158,"lat":65.597},{"n":"Sundsvall","r":4,"cap":0,"mega":0,"pop":73389,"lon":17.317,"lat":62.4},{"n":"Iasi","r":4,"cap":0,"mega":0,"pop":325914,"lon":27.575,"lat":47.168},{"n":"Surat Thani","r":4,"cap":0,"mega":0,"pop":157627,"lon":99.34,"lat":9.15},{"n":"Chiang Mai","r":4,"cap":0,"mega":0,"pop":397211,"lon":98.98,"lat":18.8},{"n":"Nakhon Ratchasima","r":4,"cap":0,"mega":0,"pop":334984,"lon":102.1,"lat":15.0},{"n":"Mbabane","r":4,"cap":1,"mega":0,"pop":90138,"lon":31.133,"lat":-26.317},{"n":"Piura","r":4,"cap":0,"mega":0,"pop":396932,"lon":-80.63,"lat":-5.21},{"n":"Arequipa","r":4,"cap":0,"mega":1,"pop":815000,"lon":-71.532,"lat":-16.418},{"n":"Chimbote","r":4,"cap":0,"mega":0,"pop":349846,"lon":-78.57,"lat":-9.07},{"n":"Pucallpa","r":4,"cap":0,"mega":0,"pop":310750,"lon":-74.535,"lat":-8.369},{"n":"Iquitos","r":4,"cap":0,"mega":0,"pop":458729,"lon":-73.25,"lat":-3.75},{"n":"Huancayo","r":4,"cap":0,"mega":0,"pop":412733,"lon":-75.2,"lat":-12.08},{"n":"Ciudad del Este","r":4,"cap":0,"mega":0,"pop":320872,"lon":-54.616,"lat":-25.517},{"n":"Ponta Delgada","r":4,"cap":0,"mega":0,"pop":61526,"lon":-25.667,"lat":37.748},{"n":"Vigo","r":4,"cap":0,"mega":0,"pop":378952,"lon":-8.73,"lat":42.22},{"n":"Bilbao","r":4,"cap":0,"mega":0,"pop":875552,"lon":-2.93,"lat":43.25},{"n":"Kaolack","r":4,"cap":0,"mega":0,"pop":277812,"lon":-16.1,"lat":14.15},{"n":"Kaedi","r":4,"cap":0,"mega":0,"pop":21656,"lon":-13.5,"lat":16.15},{"n":"Geneina","r":4,"cap":0,"mega":0,"pop":162981,"lon":22.44,"lat":13.45},{"n":"Medina","r":4,"cap":0,"mega":1,"pop":1010000,"lon":39.578,"lat":24.502},{"n":"Tabuk","r":4,"cap":0,"mega":0,"pop":547957,"lon":36.555,"lat":28.384},{"n":"Juba","r":4,"cap":0,"mega":0,"pop":111975,"lon":31.58,"lat":4.83},{"n":"Malakal","r":4,"cap":0,"mega":0,"pop":160765,"lon":31.656,"lat":9.537},{"n":"Omdurman","r":4,"cap":0,"mega":0,"pop":2395159,"lon":32.48,"lat":15.617},{"n":"El Obeid","r":4,"cap":0,"mega":0,"pop":393311,"lon":30.217,"lat":13.183},{"n":"The Hague","r":4,"cap":0,"mega":0,"pop":1406000,"lon":4.27,"lat":52.08},{"n":"Kristiansand","r":4,"cap":0,"mega":0,"pop":63814,"lon":7.975,"lat":58.146},{"n":"Ljubljana","r":4,"cap":1,"mega":0,"pop":314807,"lon":14.515,"lat":46.055},{"n":"Bratislava","r":4,"cap":1,"mega":0,"pop":423737,"lon":17.117,"lat":48.15},{"n":"Hammerfest","r":4,"cap":0,"mega":0,"pop":10000,"lon":23.688,"lat":70.661},{"n":"Doha","r":4,"cap":1,"mega":0,"pop":1450000,"lon":51.533,"lat":25.287},{"n":"Quetta","r":4,"cap":0,"mega":1,"pop":768000,"lon":67.023,"lat":30.222},{"n":"Larkana","r":4,"cap":0,"mega":0,"pop":364033,"lon":68.207,"lat":27.562},{"n":"Springbok","r":4,"cap":0,"mega":0,"pop":10438,"lon":17.883,"lat":-29.667},{"n":"Upington","r":4,"cap":0,"mega":0,"pop":71373,"lon":21.23,"lat":-28.46},{"n":"Worcester","r":4,"cap":0,"mega":0,"pop":127597,"lon":19.44,"lat":-33.64},{"n":"George","r":4,"cap":0,"mega":0,"pop":174582,"lon":22.45,"lat":-33.95},{"n":"Tete","r":4,"cap":0,"mega":0,"pop":129316,"lon":33.58,"lat":-16.17},{"n":"Pemba","r":4,"cap":0,"mega":0,"pop":110643,"lon":40.532,"lat":-12.983},{"n":"Nampula","r":4,"cap":0,"mega":0,"pop":388526,"lon":39.293,"lat":-15.136},{"n":"Welkom","r":4,"cap":0,"mega":0,"pop":431944,"lon":26.73,"lat":-27.97},{"n":"Xai-Xai","r":4,"cap":0,"mega":0,"pop":128805,"lon":33.64,"lat":-25.04},{"n":"Goroka","r":4,"cap":0,"mega":0,"pop":39699,"lon":145.385,"lat":-6.083},{"n":"Mt. Hagen","r":4,"cap":0,"mega":0,"pop":59064,"lon":144.217,"lat":-5.863},{"n":"Rabaul","r":4,"cap":0,"mega":0,"pop":8074,"lon":152.143,"lat":-4.205},{"n":"Lae","r":4,"cap":0,"mega":0,"pop":131052,"lon":146.99,"lat":-6.733},{"n":"David","r":4,"cap":0,"mega":0,"pop":110037,"lon":-82.433,"lat":8.433},{"n":"Oujda","r":4,"cap":0,"mega":0,"pop":409391,"lon":-1.91,"lat":34.69},{"n":"Safi","r":4,"cap":0,"mega":0,"pop":353476,"lon":-9.24,"lat":32.32},{"n":"Podgorica","r":4,"cap":1,"mega":0,"pop":145850,"lon":19.266,"lat":42.466},{"n":"Quelimane","r":4,"cap":0,"mega":0,"pop":188964,"lon":36.89,"lat":-17.88},{"n":"East London","r":4,"cap":0,"mega":0,"pop":338627,"lon":27.87,"lat":-32.97},{"n":"Middelburg","r":4,"cap":0,"mega":0,"pop":18164,"lon":25.01,"lat":-31.5},{"n":"Naltchik","r":4,"cap":0,"mega":0,"pop":307866,"lon":43.618,"lat":43.498},{"n":"Stavropol","r":4,"cap":0,"mega":0,"pop":363064,"lon":41.98,"lat":45.05},{"n":"Ugolnye Kopi","r":4,"cap":0,"mega":0,"pop":3367,"lon":177.7,"lat":64.733},{"n":"Kaliningrad","r":4,"cap":0,"mega":0,"pop":434954,"lon":20.497,"lat":54.7},{"n":"Pskov","r":4,"cap":0,"mega":0,"pop":201990,"lon":28.33,"lat":57.83},{"n":"Bryansk","r":4,"cap":0,"mega":0,"pop":427236,"lon":34.43,"lat":53.26},{"n":"Smolensk","r":4,"cap":0,"mega":0,"pop":320991,"lon":32.047,"lat":54.783},{"n":"Petrozavodsk","r":4,"cap":0,"mega":0,"pop":265025,"lon":34.361,"lat":61.781},{"n":"Tver","r":4,"cap":0,"mega":0,"pop":400212,"lon":35.89,"lat":56.86},{"n":"Vologda","r":4,"cap":0,"mega":0,"pop":294889,"lon":39.92,"lat":59.21},{"n":"Yaroslavl","r":4,"cap":0,"mega":0,"pop":606730,"lon":39.87,"lat":57.62},{"n":"Rostov","r":4,"cap":0,"mega":1,"pop":1052000,"lon":39.711,"lat":47.237},{"n":"Sochi","r":4,"cap":0,"mega":0,"pop":327608,"lon":39.73,"lat":43.59},{"n":"Krasnodar","r":4,"cap":0,"mega":0,"pop":649851,"lon":39.0,"lat":45.02},{"n":"Penza","r":4,"cap":0,"mega":0,"pop":512602,"lon":45.0,"lat":53.18},{"n":"Ryazan","r":4,"cap":0,"mega":0,"pop":520173,"lon":39.72,"lat":54.62},{"n":"Voronezh","r":4,"cap":0,"mega":1,"pop":844000,"lon":39.187,"lat":51.665},{"n":"Magnitogorsk","r":4,"cap":0,"mega":0,"pop":413351,"lon":58.98,"lat":53.423},{"n":"Chelyabinsk","r":4,"cap":0,"mega":1,"pop":1091000,"lon":61.437,"lat":55.157},{"n":"Vorkuta","r":4,"cap":0,"mega":0,"pop":80039,"lon":64.01,"lat":67.5},{"n":"Kirov","r":4,"cap":0,"mega":0,"pop":457437,"lon":49.67,"lat":58.59},{"n":"Nizhny Tagil","r":4,"cap":0,"mega":0,"pop":381116,"lon":59.975,"lat":57.92},{"n":"Astrakhan","r":4,"cap":0,"mega":0,"pop":502533,"lon":48.055,"lat":46.349},{"n":"Orenburg","r":4,"cap":0,"mega":0,"pop":550204,"lon":55.11,"lat":51.78},{"n":"Saratov","r":4,"cap":0,"mega":1,"pop":843000,"lon":46.028,"lat":51.582},{"n":"Ulyanovsk","r":4,"cap":0,"mega":0,"pop":640680,"lon":48.41,"lat":54.33},{"n":"Omsk","r":4,"cap":0,"mega":1,"pop":1135000,"lon":73.398,"lat":54.992},{"n":"Tyumen","r":4,"cap":0,"mega":0,"pop":519119,"lon":65.53,"lat":57.14},{"n":"Novokuznetsk","r":4,"cap":0,"mega":0,"pop":539616,"lon":87.115,"lat":53.75},{"n":"Kemerovo","r":4,"cap":0,"mega":0,"pop":477090,"lon":86.09,"lat":55.34},{"n":"Groznyy","r":4,"cap":0,"mega":0,"pop":226100,"lon":45.699,"lat":43.319},{"n":"Kandy","r":4,"cap":0,"mega":0,"pop":111701,"lon":80.67,"lat":7.28},{"n":"Sri Jawewardenepura Kotte","r":4,"cap":0,"mega":0,"pop":115826,"lon":79.95,"lat":6.9},{"n":"Daejeon","r":4,"cap":0,"mega":1,"pop":1468000,"lon":127.423,"lat":36.337},{"n":"Gwangju","r":4,"cap":0,"mega":1,"pop":1440000,"lon":126.908,"lat":35.173},{"n":"Busan","r":4,"cap":0,"mega":1,"pop":3480000,"lon":129.008,"lat":35.097},{"n":"Zamboanga","r":4,"cap":0,"mega":1,"pop":773000,"lon":122.078,"lat":6.922},{"n":"Laoag","r":4,"cap":0,"mega":0,"pop":207048,"lon":120.594,"lat":18.199},{"n":"Baguio City","r":4,"cap":0,"mega":0,"pop":447824,"lon":120.57,"lat":16.43},{"n":"General Santos","r":4,"cap":0,"mega":0,"pop":950530,"lon":125.175,"lat":6.111},{"n":"Ust-Ulimsk","r":4,"cap":0,"mega":0,"pop":100271,"lon":102.633,"lat":57.99},{"n":"Angarsk","r":4,"cap":0,"mega":0,"pop":243158,"lon":103.92,"lat":52.56},{"n":"Abakan","r":4,"cap":0,"mega":0,"pop":167289,"lon":91.445,"lat":53.704},{"n":"Norilsk","r":4,"cap":0,"mega":0,"pop":165873,"lon":88.225,"lat":69.34},{"n":"Khatanga","r":4,"cap":0,"mega":0,"pop":3205,"lon":102.465,"lat":72.041},{"n":"Kyzyl","r":4,"cap":0,"mega":0,"pop":108240,"lon":94.383,"lat":51.707},{"n":"Ulan Ude","r":4,"cap":0,"mega":0,"pop":360278,"lon":107.625,"lat":51.825},{"n":"Blagoveshchensk","r":4,"cap":0,"mega":0,"pop":221296,"lon":127.533,"lat":50.267},{"n":"Bukachacha","r":4,"cap":0,"mega":0,"pop":3372,"lon":116.917,"lat":52.983},{"n":"Dalnegorsk","r":4,"cap":0,"mega":0,"pop":8123,"lon":135.517,"lat":44.537},{"n":"Ambarchik","r":4,"cap":0,"mega":0,"pop":0,"lon":162.334,"lat":69.651},{"n":"Batagay","r":4,"cap":0,"mega":0,"pop":4266,"lon":134.635,"lat":67.656},{"n":"Chokurdakh","r":4,"cap":0,"mega":0,"pop":2506,"lon":147.895,"lat":70.618},{"n":"Ust Nera","r":4,"cap":0,"mega":0,"pop":9148,"lon":143.2,"lat":64.567},{"n":"Lensk","r":4,"cap":0,"mega":0,"pop":25589,"lon":114.947,"lat":60.725},{"n":"Aldan","r":4,"cap":0,"mega":0,"pop":24426,"lon":125.389,"lat":58.603},{"n":"Mirnyy","r":4,"cap":0,"mega":0,"pop":40308,"lon":113.961,"lat":62.54},{"n":"Zhigansk","r":4,"cap":0,"mega":0,"pop":3237,"lon":123.371,"lat":66.77},{"n":"Okhotsk","r":4,"cap":0,"mega":0,"pop":5570,"lon":143.217,"lat":59.383},{"n":"Khabarovsk","r":4,"cap":0,"mega":0,"pop":579000,"lon":135.12,"lat":48.455},{"n":"Okha","r":4,"cap":0,"mega":0,"pop":26560,"lon":142.948,"lat":53.574},{"n":"Yuzhno Sakhalinsk","r":4,"cap":0,"mega":0,"pop":176484,"lon":142.74,"lat":46.965},{"n":"Mexicali","r":4,"cap":0,"mega":1,"pop":885000,"lon":-115.482,"lat":32.652},{"n":"La Paz","r":4,"cap":0,"mega":0,"pop":189767,"lon":-110.32,"lat":24.14},{"n":"Torreon","r":4,"cap":0,"mega":1,"pop":1144000,"lon":-103.422,"lat":25.572},{"n":"Culiacan","r":4,"cap":0,"mega":1,"pop":809000,"lon":-107.382,"lat":24.832},{"n":"Nogales","r":4,"cap":0,"mega":0,"pop":178097,"lon":-110.945,"lat":31.305},{"n":"Hermosillo","r":4,"cap":0,"mega":0,"pop":595811,"lon":-110.954,"lat":29.099},{"n":"Guaymas","r":4,"cap":0,"mega":0,"pop":103449,"lon":-110.89,"lat":27.93},{"n":"San Luis Potosi","r":4,"cap":0,"mega":1,"pop":992000,"lon":-101.002,"lat":22.172},{"n":"Matamoros","r":4,"cap":0,"mega":0,"pop":538785,"lon":-97.5,"lat":25.88},{"n":"Nuevo Laredo","r":4,"cap":0,"mega":0,"pop":349550,"lon":-99.55,"lat":27.5},{"n":"Colima","r":4,"cap":0,"mega":0,"pop":223287,"lon":-103.72,"lat":19.23},{"n":"Campeche","r":4,"cap":0,"mega":0,"pop":205212,"lon":-90.5,"lat":19.83},{"n":"Oaxaca","r":4,"cap":0,"mega":0,"pop":530728,"lon":-96.67,"lat":17.083},{"n":"Leon","r":4,"cap":0,"mega":1,"pop":1488000,"lon":-101.702,"lat":21.152},{"n":"Maiduguri","r":4,"cap":0,"mega":1,"pop":896000,"lon":13.158,"lat":11.852},{"n":"Port Harcourt","r":4,"cap":0,"mega":1,"pop":1020000,"lon":7.008,"lat":4.812},{"n":"Makurdi","r":4,"cap":0,"mega":0,"pop":292645,"lon":8.53,"lat":7.73},{"n":"Ibadan","r":4,"cap":0,"mega":1,"pop":2628000,"lon":3.928,"lat":7.382},{"n":"Ogbomosho","r":4,"cap":0,"mega":1,"pop":951000,"lon":4.238,"lat":8.132},{"n":"Warri","r":4,"cap":0,"mega":0,"pop":830106,"lon":5.76,"lat":5.52},{"n":"Kaduna","r":4,"cap":0,"mega":1,"pop":1442000,"lon":7.438,"lat":10.522},{"n":"Gdansk","r":4,"cap":0,"mega":0,"pop":740000,"lon":18.64,"lat":54.36},{"n":"Krakow","r":4,"cap":0,"mega":1,"pop":756000,"lon":19.958,"lat":50.062},{"n":"Dalandzadgad","r":4,"cap":0,"mega":0,"pop":15093,"lon":104.44,"lat":43.584},{"n":"Wonsan","r":4,"cap":0,"mega":0,"pop":329207,"lon":127.431,"lat":39.16},{"n":"Sinuiju","r":4,"cap":0,"mega":0,"pop":288112,"lon":124.421,"lat":40.086},{"n":"Dund-Us","r":4,"cap":0,"mega":0,"pop":30500,"lon":91.633,"lat":48.017},{"n":"Choybalsan","r":4,"cap":0,"mega":0,"pop":33376,"lon":114.506,"lat":48.067},{"n":"Luderitz","r":4,"cap":0,"mega":0,"pop":15137,"lon":15.159,"lat":-26.648},{"n":"Walvis Bay","r":4,"cap":0,"mega":0,"pop":52058,"lon":14.505,"lat":-22.958},{"n":"Mwanza","r":4,"cap":0,"mega":0,"pop":493944,"lon":32.93,"lat":-2.52},{"n":"Morogoro","r":4,"cap":0,"mega":0,"pop":250902,"lon":37.66,"lat":-6.82},{"n":"Dodoma","r":4,"cap":0,"mega":0,"pop":218269,"lon":35.75,"lat":-6.183},{"n":"Arusha","r":4,"cap":0,"mega":0,"pop":341136,"lon":36.67,"lat":-3.36},{"n":"Bern","r":4,"cap":1,"mega":0,"pop":275329,"lon":7.467,"lat":46.917},{"n":"Malmo","r":4,"cap":0,"mega":0,"pop":269349,"lon":13.023,"lat":55.599},{"n":"Laayoune","r":4,"cap":0,"mega":0,"pop":188084,"lon":-13.2,"lat":27.15},{"n":"Ternate","r":4,"cap":0,"mega":0,"pop":187521,"lon":127.363,"lat":0.793},{"n":"Ambon","r":4,"cap":0,"mega":0,"pop":355596,"lon":128.2,"lat":-3.717},{"n":"Raba","r":4,"cap":0,"mega":0,"pop":106101,"lon":118.767,"lat":-8.45},{"n":"Jayapura","r":4,"cap":0,"mega":0,"pop":169341,"lon":140.7,"lat":-2.533},{"n":"Florence","r":4,"cap":0,"mega":0,"pop":1500000,"lon":11.25,"lat":43.78},{"n":"Catania","r":4,"cap":0,"mega":0,"pop":674542,"lon":15.08,"lat":37.5},{"n":"Pristina","r":4,"cap":1,"mega":0,"pop":465186,"lon":21.166,"lat":42.667},{"n":"Meru","r":4,"cap":0,"mega":0,"pop":47226,"lon":37.64,"lat":0.06},{"n":"Eldoret","r":4,"cap":0,"mega":0,"pop":353381,"lon":35.27,"lat":0.52},{"n":"Banda Aceh","r":4,"cap":0,"mega":0,"pop":513698,"lon":95.32,"lat":5.55},{"n":"George Town","r":4,"cap":0,"mega":0,"pop":2500000,"lon":100.329,"lat":5.414},{"n":"Zhangye","r":4,"cap":0,"mega":0,"pop":230728,"lon":100.45,"lat":38.93},{"n":"Wuwei","r":4,"cap":0,"mega":0,"pop":493092,"lon":102.641,"lat":37.928},{"n":"Dunhuang","r":4,"cap":0,"mega":0,"pop":140094,"lon":94.662,"lat":40.143},{"n":"Tianshui","r":4,"cap":0,"mega":1,"pop":1225000,"lon":105.918,"lat":34.602},{"n":"Dulan","r":4,"cap":0,"mega":0,"pop":100,"lon":98.267,"lat":36.167},{"n":"Golmud","r":4,"cap":0,"mega":0,"pop":107092,"lon":94.883,"lat":36.417},{"n":"Yulin","r":4,"cap":0,"mega":1,"pop":1127000,"lon":110.148,"lat":22.632},{"n":"Bose","r":4,"cap":0,"mega":0,"pop":175282,"lon":106.613,"lat":23.9},{"n":"Wuzhou","r":4,"cap":0,"mega":0,"pop":442315,"lon":111.32,"lat":23.48},{"n":"Liupanshui","r":4,"cap":0,"mega":0,"pop":1221000,"lon":104.831,"lat":26.596},{"n":"Quanzhou","r":4,"cap":0,"mega":1,"pop":1463000,"lon":118.578,"lat":24.902},{"n":"Hefei","r":4,"cap":0,"mega":1,"pop":2035000,"lon":117.278,"lat":31.852},{"n":"Suzhou","r":4,"cap":0,"mega":1,"pop":1964000,"lon":116.977,"lat":33.638},{"n":"Zhanjiang","r":4,"cap":0,"mega":1,"pop":1590000,"lon":110.378,"lat":21.202},{"n":"Shaoguan","r":4,"cap":0,"mega":0,"pop":720266,"lon":113.58,"lat":24.8},{"n":"Balikpapan","r":4,"cap":0,"mega":0,"pop":445905,"lon":116.83,"lat":-1.25},{"n":"Kuching","r":4,"cap":0,"mega":0,"pop":570407,"lon":110.33,"lat":1.53},{"n":"Antsiranana","r":4,"cap":0,"mega":0,"pop":82937,"lon":49.312,"lat":-12.277},{"n":"Fianarantsoa","r":4,"cap":0,"mega":0,"pop":184184,"lon":47.083,"lat":-21.433},{"n":"Mahajanga","r":4,"cap":0,"mega":0,"pop":154657,"lon":46.345,"lat":-15.67},{"n":"Toliara","r":4,"cap":0,"mega":0,"pop":115319,"lon":43.69,"lat":-23.357},{"n":"Surakarta","r":4,"cap":0,"mega":0,"pop":555308,"lon":110.825,"lat":-7.565},{"n":"Bandar Lampung","r":4,"cap":0,"mega":1,"pop":865000,"lon":105.268,"lat":-5.428},{"n":"Tanjungpandan","r":4,"cap":0,"mega":0,"pop":62374,"lon":107.65,"lat":-2.75},{"n":"Malang","r":4,"cap":0,"mega":1,"pop":805000,"lon":112.608,"lat":-7.978},{"n":"Kupang","r":4,"cap":0,"mega":0,"pop":282396,"lon":123.583,"lat":-10.179},{"n":"Parepare","r":4,"cap":0,"mega":0,"pop":87776,"lon":119.633,"lat":-4.017},{"n":"Cuenca","r":4,"cap":0,"mega":0,"pop":286878,"lon":-79.0,"lat":-2.9},{"n":"Santa Cruz","r":4,"cap":0,"mega":0,"pop":11262,"lon":-90.35,"lat":-0.533},{"n":"Puerto Limon","r":4,"cap":0,"mega":0,"pop":85001,"lon":-83.033,"lat":10.0},{"n":"Santiago de Cuba","r":4,"cap":0,"mega":0,"pop":555865,"lon":-75.821,"lat":20.025},{"n":"Santiago","r":4,"cap":0,"mega":0,"pop":1550753,"lon":-70.67,"lat":19.5},{"n":"Manizales","r":4,"cap":0,"mega":0,"pop":375848,"lon":-75.52,"lat":5.06},{"n":"Pasto","r":4,"cap":0,"mega":0,"pop":382236,"lon":-77.281,"lat":1.214},{"n":"Barranquilla","r":4,"cap":0,"mega":1,"pop":1798000,"lon":-74.802,"lat":10.962},{"n":"Roseau","r":4,"cap":1,"mega":0,"pop":23336,"lon":-61.387,"lat":15.301},{"n":"Mbandaka","r":4,"cap":0,"mega":0,"pop":274996,"lon":18.26,"lat":0.04},{"n":"Moundou","r":4,"cap":0,"mega":0,"pop":156705,"lon":16.09,"lat":8.55},{"n":"Suez","r":4,"cap":0,"mega":0,"pop":508335,"lon":32.55,"lat":30.005},{"n":"Bur Said","r":4,"cap":0,"mega":0,"pop":623864,"lon":32.29,"lat":31.26},{"n":"El Faiyum","r":4,"cap":0,"mega":0,"pop":316772,"lon":30.84,"lat":29.31},{"n":"Aswan","r":4,"cap":0,"mega":0,"pop":313442,"lon":32.899,"lat":24.088},{"n":"Asyut","r":4,"cap":0,"mega":0,"pop":420585,"lon":31.18,"lat":27.19},{"n":"Kisangani","r":4,"cap":0,"mega":0,"pop":578470,"lon":25.22,"lat":0.52},{"n":"Assab","r":4,"cap":0,"mega":0,"pop":105496,"lon":42.73,"lat":13.01},{"n":"Djibouti","r":4,"cap":1,"mega":0,"pop":923000,"lon":43.148,"lat":11.595},{"n":"Dresden","r":4,"cap":0,"mega":0,"pop":617515,"lon":13.75,"lat":51.05},{"n":"Xigaze","r":4,"cap":0,"mega":0,"pop":80000,"lon":88.883,"lat":29.25},{"n":"Shache","r":4,"cap":0,"mega":0,"pop":851374,"lon":77.25,"lat":38.426},{"n":"Yining","r":4,"cap":0,"mega":0,"pop":542507,"lon":81.35,"lat":43.9},{"n":"Altay","r":4,"cap":0,"mega":0,"pop":142000,"lon":88.117,"lat":47.867},{"n":"Putrajaya","r":4,"cap":0,"mega":0,"pop":67964,"lon":101.695,"lat":2.933},{"n":"Shizuishan","r":4,"cap":0,"mega":0,"pop":136570,"lon":106.769,"lat":39.233},{"n":"Yulin","r":4,"cap":0,"mega":0,"pop":155960,"lon":109.733,"lat":38.283},{"n":"Ankang","r":4,"cap":0,"mega":0,"pop":1100000,"lon":109.02,"lat":32.68},{"n":"Houma","r":4,"cap":0,"mega":0,"pop":102400,"lon":111.21,"lat":35.62},{"n":"Yueyang","r":4,"cap":0,"mega":1,"pop":826000,"lon":113.098,"lat":29.382},{"n":"Hengyang","r":4,"cap":0,"mega":1,"pop":1016000,"lon":112.588,"lat":26.882},{"n":"Mianyang","r":4,"cap":0,"mega":1,"pop":1396000,"lon":104.69,"lat":31.465},{"n":"Xichang","r":4,"cap":0,"mega":0,"pop":379993,"lon":102.3,"lat":27.88},{"n":"Baoshan","r":4,"cap":0,"mega":0,"pop":1000000,"lon":99.15,"lat":25.12},{"n":"Gejiu","r":4,"cap":0,"mega":0,"pop":149105,"lon":103.15,"lat":23.38},{"n":"Shijiazhuang","r":4,"cap":0,"mega":1,"pop":2417000,"lon":114.478,"lat":38.052},{"n":"Handan","r":4,"cap":0,"mega":1,"pop":1631000,"lon":114.478,"lat":36.582},{"n":"Anshan","r":4,"cap":0,"mega":1,"pop":1639000,"lon":122.938,"lat":41.117},{"n":"Dalian","r":4,"cap":0,"mega":1,"pop":3167000,"lon":121.628,"lat":38.925},{"n":"Qingdao","r":4,"cap":0,"mega":1,"pop":2866000,"lon":120.328,"lat":36.092},{"n":"Linyi","r":4,"cap":0,"mega":1,"pop":2082000,"lon":118.328,"lat":35.082},{"n":"Huaiyin","r":4,"cap":0,"mega":1,"pop":1264000,"lon":119.066,"lat":33.585},{"n":"Wenzhou","r":4,"cap":0,"mega":1,"pop":2350000,"lon":120.648,"lat":28.022},{"n":"Ningbo","r":4,"cap":0,"mega":1,"pop":1923000,"lon":121.548,"lat":29.882},{"n":"Fukuoka","r":4,"cap":0,"mega":1,"pop":2792000,"lon":130.408,"lat":33.597},{"n":"Miyazaki","r":4,"cap":0,"mega":0,"pop":324384,"lon":131.418,"lat":31.918},{"n":"Naha","r":4,"cap":0,"mega":0,"pop":905238,"lon":127.673,"lat":26.207},{"n":"Kochi","r":4,"cap":0,"mega":0,"pop":335570,"lon":133.538,"lat":33.562},{"n":"Gorontalo","r":4,"cap":0,"mega":0,"pop":365497,"lon":123.07,"lat":0.55},{"n":"Tongliao","r":4,"cap":0,"mega":1,"pop":884000,"lon":122.268,"lat":43.622},{"n":"Hohhot","r":4,"cap":0,"mega":1,"pop":1726000,"lon":111.658,"lat":40.822},{"n":"Chifeng","r":4,"cap":0,"mega":1,"pop":1277000,"lon":118.948,"lat":42.272},{"n":"Ulanhot","r":4,"cap":0,"mega":0,"pop":241894,"lon":122.08,"lat":46.08},{"n":"Hailar","r":4,"cap":0,"mega":0,"pop":231171,"lon":119.7,"lat":49.2},{"n":"Jiamusi","r":4,"cap":0,"mega":1,"pop":1020000,"lon":130.348,"lat":46.832},{"n":"Beian","r":4,"cap":0,"mega":0,"pop":154936,"lon":126.529,"lat":48.246},{"n":"Daqing","r":4,"cap":0,"mega":1,"pop":1693000,"lon":124.998,"lat":46.582},{"n":"Jixi","r":4,"cap":0,"mega":1,"pop":965000,"lon":130.968,"lat":45.302},{"n":"Nagoya","r":4,"cap":0,"mega":1,"pop":3230000,"lon":136.913,"lat":35.157},{"n":"Nagano","r":4,"cap":0,"mega":0,"pop":594311,"lon":138.17,"lat":36.65},{"n":"Kushiro","r":4,"cap":0,"mega":0,"pop":198566,"lon":144.375,"lat":42.975},{"n":"Hakodate","r":4,"cap":0,"mega":0,"pop":302984,"lon":140.74,"lat":41.795},{"n":"Kyoto","r":4,"cap":0,"mega":1,"pop":1805000,"lon":135.748,"lat":35.032},{"n":"Sendai","r":4,"cap":0,"mega":1,"pop":2250000,"lon":140.87,"lat":38.268},{"n":"Sakata","r":4,"cap":0,"mega":0,"pop":100446,"lon":139.85,"lat":38.92},{"n":"Bandundu","r":4,"cap":0,"mega":0,"pop":118211,"lon":17.38,"lat":-3.31},{"n":"Kananga","r":4,"cap":0,"mega":1,"pop":765000,"lon":22.398,"lat":-5.888},{"n":"Kasongo","r":4,"cap":0,"mega":0,"pop":63000,"lon":26.66,"lat":-4.45},{"n":"Mbuji-Mayi","r":4,"cap":0,"mega":1,"pop":1295000,"lon":23.598,"lat":-6.148},{"n":"Kalemie","r":4,"cap":0,"mega":0,"pop":206257,"lon":29.2,"lat":-5.933},{"n":"Butembo","r":4,"cap":0,"mega":0,"pop":286403,"lon":29.28,"lat":0.13},{"n":"Goma","r":4,"cap":0,"mega":0,"pop":144124,"lon":29.222,"lat":-1.679},{"n":"Mzuzu","r":4,"cap":0,"mega":0,"pop":110201,"lon":34.02,"lat":-11.46},{"n":"Blantyre","r":4,"cap":0,"mega":0,"pop":584877,"lon":34.99,"lat":-15.79},{"n":"Quetzaltenango","r":4,"cap":0,"mega":0,"pop":575263,"lon":-91.52,"lat":14.83},{"n":"Banjul","r":4,"cap":1,"mega":0,"pop":43094,"lon":-16.592,"lat":13.454},{"n":"Faridabad","r":4,"cap":0,"mega":1,"pop":1394000,"lon":77.315,"lat":28.435},{"n":"Srinagar","r":4,"cap":0,"mega":1,"pop":1140000,"lon":74.828,"lat":34.069},{"n":"Vijayawada","r":4,"cap":0,"mega":1,"pop":1137000,"lon":80.628,"lat":16.522},{"n":"Thiruvananthapuram","r":4,"cap":0,"mega":1,"pop":954000,"lon":76.925,"lat":8.516},{"n":"Kochi","r":4,"cap":0,"mega":1,"pop":1519000,"lon":76.222,"lat":10.017},{"n":"Cuttack","r":4,"cap":0,"mega":0,"pop":580000,"lon":85.89,"lat":20.47},{"n":"Hubballi","r":4,"cap":0,"mega":1,"pop":890000,"lon":75.123,"lat":15.362},{"n":"Mangaluru","r":4,"cap":0,"mega":0,"pop":776632,"lon":74.85,"lat":12.9},{"n":"Mysuru","r":4,"cap":0,"mega":1,"pop":887000,"lon":76.658,"lat":12.312},{"n":"Kalaburagi","r":4,"cap":0,"mega":0,"pop":483615,"lon":76.82,"lat":17.35},{"n":"Kolhapur","r":4,"cap":0,"mega":0,"pop":750000,"lon":74.22,"lat":16.7},{"n":"Nanded","r":4,"cap":0,"mega":0,"pop":623708,"lon":77.308,"lat":19.197},{"n":"Akola","r":4,"cap":0,"mega":0,"pop":503502,"lon":77.01,"lat":20.71},{"n":"Guwahati","r":4,"cap":0,"mega":1,"pop":983000,"lon":91.768,"lat":26.162},{"n":"Kayes","r":4,"cap":0,"mega":0,"pop":62521,"lon":13.28,"lat":-4.18},{"n":"Franceville","r":4,"cap":0,"mega":0,"pop":42967,"lon":13.583,"lat":-1.633},{"n":"Bordeaux","r":4,"cap":0,"mega":1,"pop":803000,"lon":-0.597,"lat":44.852},{"n":"Marseille","r":4,"cap":0,"mega":1,"pop":1400000,"lon":5.373,"lat":43.292},{"n":"Le Havre","r":4,"cap":0,"mega":0,"pop":242124,"lon":0.105,"lat":49.505},{"n":"Gao","r":4,"cap":0,"mega":0,"pop":116967,"lon":-0.05,"lat":16.267},{"n":"Coihaique","r":4,"cap":0,"mega":0,"pop":45787,"lon":-72.07,"lat":-45.57},{"n":"Arica","r":4,"cap":0,"mega":0,"pop":185999,"lon":-70.29,"lat":-18.5},{"n":"Copiapo","r":4,"cap":0,"mega":0,"pop":129280,"lon":-70.33,"lat":-27.372},{"n":"La Serena","r":4,"cap":0,"mega":0,"pop":154521,"lon":-71.25,"lat":-29.9},{"n":"Los Angeles","r":4,"cap":0,"mega":0,"pop":145239,"lon":-72.347,"lat":-37.48},{"n":"Narsarsuaq","r":4,"cap":0,"mega":0,"pop":145,"lon":-45.417,"lat":61.167},{"n":"Sisimiut","r":4,"cap":0,"mega":0,"pop":5227,"lon":-53.667,"lat":66.95},{"n":"Upernavik","r":4,"cap":0,"mega":0,"pop":1129,"lon":-56.142,"lat":72.709},{"n":"Qaanaaq","r":4,"cap":0,"mega":0,"pop":616,"lon":-69.23,"lat":77.467},{"n":"Nouadhibou","r":4,"cap":0,"mega":0,"pop":86738,"lon":-17.056,"lat":20.9},{"n":"Kayes","r":4,"cap":0,"mega":0,"pop":77207,"lon":-11.44,"lat":14.45},{"n":"Ayoun el Atrous","r":4,"cap":0,"mega":0,"pop":1423,"lon":-9.617,"lat":16.667},{"n":"Segou","r":4,"cap":0,"mega":0,"pop":122952,"lon":-6.26,"lat":13.44},{"n":"Skopje","r":4,"cap":1,"mega":0,"pop":494087,"lon":21.433,"lat":42.0},{"n":"Al Jawf","r":4,"cap":0,"mega":0,"pop":24132,"lon":23.29,"lat":24.2},{"n":"Tmassah","r":4,"cap":0,"mega":0,"pop":500,"lon":15.8,"lat":26.367},{"n":"Misratah","r":4,"cap":0,"mega":0,"pop":386120,"lon":15.1,"lat":32.38},{"n":"Zuwarah","r":4,"cap":0,"mega":0,"pop":180310,"lon":12.079,"lat":32.934},{"n":"Kirkuk","r":4,"cap":0,"mega":0,"pop":601433,"lon":44.392,"lat":35.472},{"n":"Mosul","r":4,"cap":0,"mega":1,"pop":1316000,"lon":43.143,"lat":36.347},{"n":"An Najaf","r":4,"cap":0,"mega":0,"pop":665552,"lon":44.335,"lat":32.0},{"n":"Bahir Dar","r":4,"cap":0,"mega":0,"pop":206748,"lon":37.383,"lat":11.6},{"n":"Mekele","r":4,"cap":0,"mega":0,"pop":95856,"lon":39.47,"lat":13.5},{"n":"Dire Dawa","r":4,"cap":0,"mega":0,"pop":252279,"lon":41.86,"lat":9.59},{"n":"Rovaniemi","r":4,"cap":0,"mega":0,"pop":34781,"lon":25.716,"lat":66.5},{"n":"Vaasa","r":4,"cap":0,"mega":0,"pop":57014,"lon":21.6,"lat":63.1},{"n":"Tampere","r":4,"cap":0,"mega":0,"pop":259279,"lon":23.75,"lat":61.5},{"n":"Aqtobe","r":4,"cap":0,"mega":0,"pop":262457,"lon":57.17,"lat":50.28},{"n":"Rudny","r":4,"cap":0,"mega":0,"pop":124000,"lon":63.13,"lat":52.953},{"n":"Qyzylorda","r":4,"cap":0,"mega":0,"pop":300000,"lon":65.465,"lat":44.8},{"n":"Atyrau","r":4,"cap":0,"mega":0,"pop":182753,"lon":51.92,"lat":47.113},{"n":"Ekibastuz","r":4,"cap":0,"mega":0,"pop":127868,"lon":75.32,"lat":51.73},{"n":"Pavlodar","r":4,"cap":0,"mega":0,"pop":329002,"lon":76.95,"lat":52.3},{"n":"Semey","r":4,"cap":0,"mega":0,"pop":311353,"lon":80.275,"lat":50.435},{"n":"Oskemen","r":4,"cap":0,"mega":0,"pop":319067,"lon":82.615,"lat":49.99},{"n":"Yazd","r":4,"cap":0,"mega":0,"pop":477905,"lon":54.37,"lat":31.92},{"n":"Ahvaz","r":4,"cap":0,"mega":1,"pop":996000,"lon":48.718,"lat":31.282},{"n":"Basra","r":4,"cap":0,"mega":1,"pop":870000,"lon":47.812,"lat":30.515},{"n":"Bandar-e-Abbas","r":4,"cap":0,"mega":0,"pop":461499,"lon":56.272,"lat":27.204},{"n":"Hamadan","r":4,"cap":0,"mega":0,"pop":528256,"lon":48.515,"lat":34.796},{"n":"Tabriz","r":4,"cap":0,"mega":1,"pop":1413000,"lon":46.299,"lat":38.088},{"n":"Ludhiana","r":4,"cap":0,"mega":1,"pop":1649000,"lon":75.87,"lat":30.93},{"n":"Kota","r":4,"cap":0,"mega":1,"pop":827000,"lon":75.833,"lat":25.182},{"n":"Jodhpur","r":4,"cap":0,"mega":1,"pop":995000,"lon":73.015,"lat":26.294},{"n":"Shymkent","r":4,"cap":0,"mega":0,"pop":465392,"lon":69.595,"lat":42.32},{"n":"Taraz","r":4,"cap":0,"mega":0,"pop":358153,"lon":71.365,"lat":42.9},{"n":"Lucknow","r":4,"cap":0,"mega":1,"pop":2695000,"lon":80.913,"lat":26.857},{"n":"Saharanpur","r":4,"cap":0,"mega":0,"pop":484873,"lon":77.55,"lat":29.97},{"n":"Ranchi","r":4,"cap":0,"mega":1,"pop":1044000,"lon":85.328,"lat":23.372},{"n":"Bhagalpur","r":4,"cap":0,"mega":0,"pop":361548,"lon":86.98,"lat":25.23},{"n":"Raipur","r":4,"cap":0,"mega":1,"pop":875000,"lon":81.633,"lat":21.237},{"n":"Jabalpur","r":4,"cap":0,"mega":1,"pop":1285000,"lon":79.953,"lat":23.177},{"n":"Indore","r":4,"cap":0,"mega":1,"pop":2026000,"lon":75.863,"lat":22.717},{"n":"Puducherry","r":4,"cap":0,"mega":0,"pop":227411,"lon":79.83,"lat":11.935},{"n":"Salem","r":4,"cap":0,"mega":1,"pop":873000,"lon":78.178,"lat":11.672},{"n":"Tiruchirappalli","r":4,"cap":0,"mega":1,"pop":951000,"lon":78.688,"lat":10.801},{"n":"Pointe-Noire","r":4,"cap":0,"mega":0,"pop":659084,"lon":11.88,"lat":-4.77},{"n":"Kankan","r":4,"cap":0,"mega":0,"pop":114009,"lon":-9.31,"lat":10.39},{"n":"Nzerekore","r":4,"cap":0,"mega":0,"pop":168121,"lon":-8.83,"lat":7.76},{"n":"Bouake","r":4,"cap":0,"mega":0,"pop":567481,"lon":-5.03,"lat":7.69},{"n":"St.-Denis","r":4,"cap":0,"mega":0,"pop":190047,"lon":55.448,"lat":-20.879},{"n":"Rio Branco","r":4,"cap":0,"mega":0,"pop":257642,"lon":-67.8,"lat":-9.967},{"n":"Sao Luis","r":4,"cap":0,"mega":1,"pop":1038000,"lon":-44.268,"lat":-2.514},{"n":"Porto Velho","r":4,"cap":0,"mega":0,"pop":306180,"lon":-63.9,"lat":-8.75},{"n":"Alvorada","r":4,"cap":0,"mega":0,"pop":10232,"lon":-49.082,"lat":-12.47},{"n":"Corumba","r":4,"cap":0,"mega":0,"pop":96520,"lon":-57.65,"lat":-19.016},{"n":"Belo Horizonte","r":4,"cap":0,"mega":1,"pop":5575000,"lon":-43.917,"lat":-19.913},{"n":"Montes Claros","r":4,"cap":0,"mega":0,"pop":332379,"lon":-43.86,"lat":-16.72},{"n":"Uberlandia","r":4,"cap":0,"mega":0,"pop":563536,"lon":-48.28,"lat":-18.9},{"n":"Colider","r":4,"cap":0,"mega":0,"pop":27139,"lon":-55.451,"lat":-10.817},{"n":"Alta Floresta","r":4,"cap":0,"mega":0,"pop":40466,"lon":-55.91,"lat":-9.9},{"n":"Cuiaba","r":4,"cap":0,"mega":1,"pop":806000,"lon":-56.087,"lat":-15.568},{"n":"Pelotas","r":4,"cap":0,"mega":0,"pop":320674,"lon":-52.33,"lat":-31.75},{"n":"Caxias do Sul","r":4,"cap":0,"mega":0,"pop":381270,"lon":-51.17,"lat":-29.18},{"n":"Ponta Grossa","r":4,"cap":0,"mega":0,"pop":292177,"lon":-50.16,"lat":-25.09},{"n":"Teresina","r":4,"cap":0,"mega":1,"pop":907000,"lon":-42.782,"lat":-5.093},{"n":"Maceio","r":4,"cap":0,"mega":1,"pop":1186000,"lon":-35.732,"lat":-9.618},{"n":"Vitoria da Conquista","r":4,"cap":0,"mega":0,"pop":308204,"lon":-40.84,"lat":-14.85},{"n":"Barreiras","r":4,"cap":0,"mega":0,"pop":158292,"lon":-45.0,"lat":-12.14},{"n":"Vila Velha","r":4,"cap":0,"mega":0,"pop":1209091,"lon":-40.29,"lat":-20.338},{"n":"Natal","r":4,"cap":0,"mega":1,"pop":1088000,"lon":-35.242,"lat":-5.778},{"n":"Thompson","r":4,"cap":0,"mega":0,"pop":13727,"lon":-97.867,"lat":55.75},{"n":"Brandon","r":4,"cap":0,"mega":0,"pop":28418,"lon":-99.95,"lat":49.833},{"n":"Fort Smith","r":4,"cap":0,"mega":0,"pop":518,"lon":-111.883,"lat":60.0},{"n":"Fort McMurray","r":4,"cap":0,"mega":0,"pop":21863,"lon":-111.383,"lat":56.733},{"n":"Peace River","r":4,"cap":0,"mega":0,"pop":5340,"lon":-117.283,"lat":56.233},{"n":"Fort St. John","r":4,"cap":0,"mega":0,"pop":18776,"lon":-120.833,"lat":56.25},{"n":"Iqaluit","r":4,"cap":0,"mega":0,"pop":6124,"lon":-68.5,"lat":63.75},{"n":"Cambridge Bay","r":4,"cap":0,"mega":0,"pop":1477,"lon":-105.033,"lat":69.117},{"n":"Kugluktuk","r":4,"cap":0,"mega":0,"pop":1302,"lon":-115.125,"lat":67.799},{"n":"Chesterfield Inlet","r":4,"cap":0,"mega":0,"pop":374,"lon":-90.7,"lat":63.338},{"n":"Arviat","r":4,"cap":0,"mega":0,"pop":1868,"lon":-94.059,"lat":61.109},{"n":"Taloyoak","r":4,"cap":0,"mega":0,"pop":774,"lon":-93.533,"lat":69.533},{"n":"Igloolik","r":4,"cap":0,"mega":0,"pop":1612,"lon":-81.794,"lat":69.257},{"n":"Dawson City","r":4,"cap":0,"mega":0,"pop":1319,"lon":-139.417,"lat":64.067},{"n":"Timmins","r":4,"cap":0,"mega":0,"pop":34974,"lon":-81.333,"lat":48.467},{"n":"North Bay","r":4,"cap":0,"mega":0,"pop":50170,"lon":-79.45,"lat":46.3},{"n":"Kuujjuarapik","r":4,"cap":0,"mega":0,"pop":1243,"lon":-77.766,"lat":55.281},{"n":"Kuujjuaq","r":4,"cap":0,"mega":0,"pop":1273,"lon":-68.4,"lat":58.1},{"n":"Sydney","r":4,"cap":0,"mega":0,"pop":37538,"lon":-60.18,"lat":46.066},{"n":"Labrador City","r":4,"cap":0,"mega":0,"pop":10313,"lon":-66.916,"lat":52.941},{"n":"Ebolowa","r":4,"cap":0,"mega":0,"pop":87875,"lon":11.15,"lat":2.9},{"n":"Bambari","r":4,"cap":0,"mega":0,"pop":62098,"lon":20.667,"lat":5.762},{"n":"Venice","r":4,"cap":0,"mega":0,"pop":270816,"lon":12.335,"lat":45.439},{"n":"El Calafate","r":4,"cap":0,"mega":0,"pop":8000,"lon":-72.3,"lat":-50.333},{"n":"San Juan","r":4,"cap":0,"mega":0,"pop":447048,"lon":-68.52,"lat":-31.55},{"n":"Rawson","r":4,"cap":0,"mega":0,"pop":26335,"lon":-65.1,"lat":-43.3},{"n":"Neuquen","r":4,"cap":0,"mega":0,"pop":242092,"lon":-68.06,"lat":-38.95},{"n":"Trinidad","r":4,"cap":0,"mega":0,"pop":84259,"lon":-64.9,"lat":-14.833},{"n":"Santa Rosa","r":4,"cap":0,"mega":0,"pop":111424,"lon":-64.3,"lat":-36.62},{"n":"San Carlos de Bariloche","r":4,"cap":0,"mega":0,"pop":95394,"lon":-71.3,"lat":-41.15},{"n":"Salta","r":4,"cap":0,"mega":0,"pop":512686,"lon":-65.417,"lat":-24.783},{"n":"Tucuman","r":4,"cap":0,"mega":1,"pop":830000,"lon":-65.219,"lat":-26.814},{"n":"Formosa","r":4,"cap":0,"mega":0,"pop":221383,"lon":-58.183,"lat":-26.173},{"n":"Santa Fe","r":4,"cap":0,"mega":0,"pop":489505,"lon":-60.69,"lat":-31.624},{"n":"Rosario","r":4,"cap":0,"mega":1,"pop":1203000,"lon":-60.668,"lat":-32.949},{"n":"Campinas","r":4,"cap":0,"mega":1,"pop":2791000,"lon":-47.056,"lat":-22.908},{"n":"Sorocaba","r":4,"cap":0,"mega":0,"pop":563281,"lon":-47.47,"lat":-23.49},{"n":"Ribeirao Preto","r":4,"cap":0,"mega":0,"pop":551267,"lon":-47.83,"lat":-21.17},{"n":"Petrolina","r":4,"cap":0,"mega":0,"pop":260985,"lon":-40.51,"lat":-9.38},{"n":"Bamenda","r":4,"cap":0,"mega":0,"pop":445299,"lon":10.15,"lat":5.96},{"n":"Garoua","r":4,"cap":0,"mega":0,"pop":436899,"lon":13.39,"lat":9.3},{"n":"Herat","r":4,"cap":0,"mega":0,"pop":481009,"lon":62.17,"lat":34.33},{"n":"Mazar-e Sharif","r":4,"cap":0,"mega":0,"pop":458151,"lon":67.1,"lat":36.7},{"n":"Battambang","r":4,"cap":0,"mega":0,"pop":154773,"lon":103.2,"lat":13.1},{"n":"Siem Reap","r":4,"cap":0,"mega":0,"pop":109398,"lon":103.85,"lat":13.367},{"n":"Malanje","r":4,"cap":0,"mega":0,"pop":125856,"lon":16.34,"lat":-9.54},{"n":"Benguela","r":4,"cap":0,"mega":0,"pop":151226,"lon":13.407,"lat":-12.578},{"n":"Lubango","r":4,"cap":0,"mega":0,"pop":125632,"lon":13.49,"lat":-14.91},{"n":"Namibe","r":4,"cap":0,"mega":0,"pop":132900,"lon":12.16,"lat":-15.19},{"n":"Tarija","r":4,"cap":0,"mega":0,"pop":159269,"lon":-64.75,"lat":-21.517},{"n":"Bridgetown","r":4,"cap":1,"mega":0,"pop":191152,"lon":-59.617,"lat":13.102},{"n":"Annaba","r":4,"cap":0,"mega":0,"pop":503524,"lon":7.76,"lat":36.92},{"n":"Parakou","r":4,"cap":0,"mega":0,"pop":188853,"lon":2.62,"lat":9.34},{"n":"Porto-Novo","r":4,"cap":0,"mega":0,"pop":300000,"lon":2.617,"lat":6.483},{"n":"Constantine","r":4,"cap":0,"mega":0,"pop":605179,"lon":6.6,"lat":36.36},{"n":"Brest","r":4,"cap":0,"mega":0,"pop":300715,"lon":23.7,"lat":52.1},{"n":"Khulna","r":4,"cap":0,"mega":1,"pop":1553000,"lon":89.558,"lat":22.842},{"n":"Francistown","r":4,"cap":0,"mega":0,"pop":89979,"lon":27.5,"lat":-21.17},{"n":"Mahalapye","r":4,"cap":0,"mega":0,"pop":50744,"lon":26.82,"lat":-23.1},{"n":"Serowe","r":4,"cap":0,"mega":0,"pop":48573,"lon":26.71,"lat":-22.39},{"n":"Katherine","r":4,"cap":0,"mega":0,"pop":10141,"lon":132.267,"lat":-14.467},{"n":"Busselton","r":4,"cap":0,"mega":0,"pop":10214,"lon":115.349,"lat":-33.656},{"n":"Mandurah","r":4,"cap":0,"mega":0,"pop":73356,"lon":115.747,"lat":-32.523},{"n":"Broome","r":4,"cap":0,"mega":0,"pop":13218,"lon":122.231,"lat":-17.962},{"n":"Kalgoorlie","r":4,"cap":0,"mega":0,"pop":36852,"lon":121.46,"lat":-30.735},{"n":"Albany","r":4,"cap":0,"mega":0,"pop":26445,"lon":117.892,"lat":-35.017},{"n":"Port Hedland","r":4,"cap":0,"mega":0,"pop":14288,"lon":118.606,"lat":-20.31},{"n":"Karratha","r":4,"cap":0,"mega":0,"pop":16796,"lon":116.87,"lat":-20.73},{"n":"Geraldton","r":4,"cap":0,"mega":0,"pop":27258,"lon":114.6,"lat":-28.767},{"n":"Griffith","r":4,"cap":0,"mega":0,"pop":15455,"lon":146.04,"lat":-34.29},{"n":"Orange","r":4,"cap":0,"mega":0,"pop":39329,"lon":149.1,"lat":-33.28},{"n":"Dubbo","r":4,"cap":0,"mega":0,"pop":31574,"lon":148.597,"lat":-32.26},{"n":"Armidale","r":4,"cap":0,"mega":0,"pop":22673,"lon":151.667,"lat":-30.512},{"n":"Broken Hill","r":4,"cap":0,"mega":0,"pop":17395,"lon":141.433,"lat":-31.95},{"n":"Port Lincoln","r":4,"cap":0,"mega":0,"pop":13630,"lon":135.867,"lat":-34.733},{"n":"Whyalla","r":4,"cap":0,"mega":0,"pop":22559,"lon":137.561,"lat":-33.025},{"n":"Portland","r":4,"cap":0,"mega":0,"pop":11808,"lon":141.59,"lat":-38.34},{"n":"Bendigo","r":4,"cap":0,"mega":0,"pop":81657,"lon":144.28,"lat":-36.76},{"n":"Wangaratta","r":4,"cap":0,"mega":0,"pop":14022,"lon":146.3,"lat":-36.36},{"n":"Windorah","r":4,"cap":0,"mega":0,"pop":158,"lon":142.65,"lat":-25.433},{"n":"Mount Isa","r":4,"cap":0,"mega":0,"pop":33200,"lon":139.49,"lat":-20.724},{"n":"Rockhampton","r":4,"cap":0,"mega":0,"pop":65850,"lon":150.52,"lat":-23.364},{"n":"Cairns","r":4,"cap":0,"mega":0,"pop":154225,"lon":145.763,"lat":-16.888},{"n":"Gold Coast","r":4,"cap":0,"mega":0,"pop":527660,"lon":153.448,"lat":-28.082},{"n":"Devonport","r":4,"cap":0,"mega":0,"pop":19317,"lon":146.331,"lat":-41.193},{"n":"Bobo Dioulasso","r":4,"cap":0,"mega":0,"pop":360106,"lon":-4.29,"lat":11.18},{"n":"Rajshahi","r":4,"cap":0,"mega":1,"pop":810000,"lon":88.603,"lat":24.377},{"n":"Mandalay","r":4,"cap":0,"mega":1,"pop":1300000,"lon":96.083,"lat":21.972},{"n":"Sittwe","r":4,"cap":0,"mega":0,"pop":179032,"lon":92.88,"lat":20.14},{"n":"Bujumbura","r":4,"cap":1,"mega":0,"pop":331700,"lon":29.36,"lat":-3.376},{"n":"Pago Pago","r":4,"cap":0,"mega":0,"pop":12576,"lon":-170.707,"lat":-14.277},{"n":"Kingstown","r":4,"cap":1,"mega":0,"pop":49485,"lon":-61.22,"lat":13.156},{"n":"Castries","r":4,"cap":1,"mega":0,"pop":37963,"lon":-60.993,"lat":14.008},{"n":"Basseterre","r":4,"cap":1,"mega":0,"pop":21887,"lon":-62.717,"lat":17.302},{"n":"Las Palmas","r":4,"cap":0,"mega":0,"pop":378495,"lon":-15.43,"lat":28.1},{"n":"Berbera","r":4,"cap":0,"mega":0,"pop":242344,"lon":45.016,"lat":10.436},{"n":"Port Louis","r":4,"cap":1,"mega":0,"pop":595491,"lon":57.5,"lat":-20.167},{"n":"Gaza","r":4,"cap":0,"mega":0,"pop":505700,"lon":34.445,"lat":31.53},{"n":"Saint George's","r":4,"cap":1,"mega":0,"pop":33734,"lon":-61.742,"lat":12.053},{"n":"Papeete","r":4,"cap":0,"mega":0,"pop":131695,"lon":-149.567,"lat":-17.533},{"n":"Manama","r":4,"cap":1,"mega":0,"pop":563920,"lon":50.583,"lat":26.236},{"n":"Freeport","r":4,"cap":0,"mega":0,"pop":25383,"lon":-78.7,"lat":26.533},{"n":"Saint John's","r":4,"cap":1,"mega":0,"pop":35499,"lon":-61.85,"lat":17.118},{"n":"Taichung","r":4,"cap":0,"mega":1,"pop":2629323,"lon":120.682,"lat":24.152},{"n":"Napier","r":4,"cap":0,"mega":0,"pop":57800,"lon":176.926,"lat":-39.49},{"n":"Manukau","r":4,"cap":0,"mega":0,"pop":375600,"lon":174.885,"lat":-37.0},{"n":"Hamilton","r":4,"cap":0,"mega":0,"pop":148200,"lon":175.29,"lat":-37.778},{"n":"Blenheim","r":4,"cap":0,"mega":0,"pop":30300,"lon":173.955,"lat":-41.514},{"n":"Dunedin","r":4,"cap":0,"mega":0,"pop":117700,"lon":170.491,"lat":-45.885},{"n":"Leh","r":4,"cap":0,"mega":0,"pop":30870,"lon":77.572,"lat":34.162},{"n":"Amaravati","r":4,"cap":0,"mega":0,"pop":5800000,"lon":80.524,"lat":16.534},{"n":"Kozhikode","r":3,"cap":0,"mega":1,"pop":953000,"lon":75.768,"lat":11.252},{"n":"Bhubaneshwar","r":3,"cap":0,"mega":1,"pop":844000,"lon":85.825,"lat":20.272},{"n":"Jamshedpur","r":3,"cap":0,"mega":1,"pop":1300000,"lon":86.196,"lat":22.789},{"n":"Montevideo","r":3,"cap":1,"mega":1,"pop":1513000,"lon":-56.187,"lat":-34.905},{"n":"Helena","r":3,"cap":0,"mega":0,"pop":38725,"lon":-112.035,"lat":46.593},{"n":"Bismarck","r":3,"cap":0,"mega":0,"pop":63871,"lon":-100.783,"lat":46.808},{"n":"Boise","r":3,"cap":0,"mega":0,"pop":338071,"lon":-116.227,"lat":43.609},{"n":"San Jose","r":3,"cap":0,"mega":1,"pop":1668000,"lon":-121.888,"lat":37.331},{"n":"Sacramento","r":3,"cap":0,"mega":1,"pop":1604000,"lon":-121.472,"lat":38.577},{"n":"Las Vegas","r":3,"cap":0,"mega":1,"pop":1823000,"lon":-115.151,"lat":36.165},{"n":"Santa Fe","r":3,"cap":0,"mega":0,"pop":92681,"lon":-105.937,"lat":35.687},{"n":"Portland","r":3,"cap":0,"mega":1,"pop":1875000,"lon":-122.682,"lat":45.522},{"n":"Salt Lake City","r":3,"cap":0,"mega":1,"pop":966000,"lon":-111.932,"lat":40.777},{"n":"Cheyenne","r":3,"cap":0,"mega":0,"pop":72927,"lon":-104.82,"lat":41.14},{"n":"Des Moines","r":3,"cap":0,"mega":0,"pop":380655,"lon":-93.62,"lat":41.58},{"n":"Omaha","r":3,"cap":0,"mega":0,"pop":877110,"lon":-96.01,"lat":41.24},{"n":"Oklahoma City","r":3,"cap":0,"mega":1,"pop":788000,"lon":-97.521,"lat":35.472},{"n":"Pierre","r":3,"cap":0,"mega":0,"pop":13879,"lon":-100.351,"lat":44.368},{"n":"San Antonio","r":3,"cap":0,"mega":1,"pop":1473000,"lon":-98.493,"lat":29.42},{"n":"San Cristobal","r":3,"cap":0,"mega":0,"pop":438798,"lon":-72.25,"lat":7.77},{"n":"Valencia","r":3,"cap":0,"mega":1,"pop":1770000,"lon":-67.982,"lat":10.232},{"n":"Jackson","r":3,"cap":0,"mega":0,"pop":250902,"lon":-90.185,"lat":32.299},{"n":"Raleigh","r":3,"cap":0,"mega":0,"pop":1163515,"lon":-78.647,"lat":35.773},{"n":"Cleveland","r":3,"cap":0,"mega":1,"pop":1890000,"lon":-81.697,"lat":41.472},{"n":"Cincinnati","r":3,"cap":0,"mega":1,"pop":1636000,"lon":-84.459,"lat":39.164},{"n":"Nashville","r":3,"cap":0,"mega":1,"pop":877000,"lon":-86.782,"lat":36.172},{"n":"Memphis","r":3,"cap":0,"mega":1,"pop":1081000,"lon":-90.029,"lat":35.145},{"n":"Norfolk","r":3,"cap":0,"mega":0,"pop":1047869,"lon":-76.28,"lat":36.85},{"n":"Milwaukee","r":3,"cap":0,"mega":1,"pop":1388000,"lon":-87.917,"lat":43.03},{"n":"Buffalo","r":3,"cap":0,"mega":1,"pop":1016000,"lon":-78.882,"lat":42.882},{"n":"Pittsburgh","r":3,"cap":0,"mega":1,"pop":1838000,"lon":-80.002,"lat":40.432},{"n":"Ciudad Guayana","r":3,"cap":0,"mega":0,"pop":746535,"lon":-62.62,"lat":8.37},{"n":"Lome","r":3,"cap":1,"mega":1,"pop":1452000,"lon":1.221,"lat":6.134},{"n":"Tunis","r":3,"cap":1,"mega":0,"pop":2412500,"lon":10.18,"lat":36.803},{"n":"Kodiak","r":3,"cap":0,"mega":0,"pop":9461,"lon":-152.407,"lat":57.79},{"n":"Cold Bay","r":3,"cap":0,"mega":0,"pop":200,"lon":-162.715,"lat":55.2},{"n":"Bethel","r":3,"cap":0,"mega":0,"pop":6228,"lon":-161.756,"lat":60.793},{"n":"Point Hope","r":3,"cap":0,"mega":0,"pop":461,"lon":-166.808,"lat":68.348},{"n":"Utqia\u0121vik","r":3,"cap":0,"mega":0,"pop":4336,"lon":-156.789,"lat":71.291},{"n":"Nome","r":3,"cap":0,"mega":0,"pop":3485,"lon":-165.406,"lat":64.506},{"n":"Valdez","r":3,"cap":0,"mega":0,"pop":4036,"lon":-146.348,"lat":61.136},{"n":"Juneau","r":3,"cap":0,"mega":0,"pop":30711,"lon":-134.401,"lat":58.303},{"n":"Fairbanks","r":3,"cap":0,"mega":0,"pop":56993,"lon":-147.711,"lat":64.837},{"n":"Prudhoe Bay","r":3,"cap":0,"mega":0,"pop":2500,"lon":-148.669,"lat":70.292},{"n":"Sevastapol","r":3,"cap":0,"mega":0,"pop":379200,"lon":33.524,"lat":44.604},{"n":"Abu Dhabi","r":3,"cap":1,"mega":0,"pop":603492,"lon":54.367,"lat":24.467},{"n":"Ashgabat","r":3,"cap":1,"mega":0,"pop":727700,"lon":58.383,"lat":37.95},{"n":"Samarqand","r":3,"cap":0,"mega":0,"pop":708000,"lon":66.945,"lat":39.67},{"n":"Lusaka","r":3,"cap":1,"mega":1,"pop":1328000,"lon":28.281,"lat":-15.415},{"n":"Harare","r":3,"cap":1,"mega":1,"pop":1572000,"lon":31.043,"lat":-17.816},{"n":"Bulawayo","r":3,"cap":0,"mega":0,"pop":699385,"lon":28.58,"lat":-20.17},{"n":"Dili","r":3,"cap":1,"mega":0,"pop":234331,"lon":125.579,"lat":-8.559},{"n":"Port Vila","r":3,"cap":1,"mega":0,"pop":44040,"lon":168.317,"lat":-17.733},{"n":"Tegucigalpa","r":3,"cap":1,"mega":1,"pop":946000,"lon":-87.219,"lat":14.104},{"n":"Georgetown","r":3,"cap":1,"mega":0,"pop":264350,"lon":-58.167,"lat":6.802},{"n":"Reykjavik","r":3,"cap":1,"mega":0,"pop":166212,"lon":-21.937,"lat":64.143},{"n":"Port-au-Prince","r":3,"cap":1,"mega":1,"pop":1998000,"lon":-72.338,"lat":18.543},{"n":"Glasgow","r":3,"cap":0,"mega":1,"pop":1160000,"lon":-4.239,"lat":55.862},{"n":"Kampala","r":3,"cap":1,"mega":1,"pop":1420000,"lon":32.581,"lat":0.319},{"n":"Aden","r":3,"cap":0,"mega":0,"pop":1000000,"lon":45.009,"lat":12.78},{"n":"Paramaribo","r":3,"cap":1,"mega":0,"pop":254169,"lon":-55.167,"lat":5.835},{"n":"Seville","r":3,"cap":0,"mega":0,"pop":1212045,"lon":-5.98,"lat":37.405},{"n":"Zinder","r":3,"cap":0,"mega":0,"pop":230358,"lon":8.983,"lat":13.8},{"n":"Niamey","r":3,"cap":1,"mega":1,"pop":915000,"lon":2.115,"lat":13.519},{"n":"Port Sudan","r":3,"cap":0,"mega":0,"pop":489725,"lon":37.216,"lat":19.616},{"n":"Dushanbe","r":3,"cap":1,"mega":0,"pop":1086244,"lon":68.774,"lat":38.56},{"n":"Cusco","r":3,"cap":0,"mega":0,"pop":361182,"lon":-71.972,"lat":-13.525},{"n":"Tacna","r":3,"cap":0,"mega":0,"pop":280098,"lon":-70.25,"lat":-18.0},{"n":"Trujillo","r":3,"cap":0,"mega":0,"pop":765171,"lon":-79.02,"lat":-8.12},{"n":"Ica","r":3,"cap":0,"mega":0,"pop":279420,"lon":-75.725,"lat":-14.068},{"n":"Asuncion","r":3,"cap":1,"mega":1,"pop":1870000,"lon":-57.626,"lat":-25.291},{"n":"Managua","r":3,"cap":1,"mega":1,"pop":920000,"lon":-86.27,"lat":12.155},{"n":"Freetown","r":3,"cap":1,"mega":1,"pop":827000,"lon":-13.236,"lat":8.472},{"n":"Agadez","r":3,"cap":0,"mega":0,"pop":117770,"lon":7.983,"lat":16.996},{"n":"Niyala","r":3,"cap":0,"mega":0,"pop":392373,"lon":24.89,"lat":12.06},{"n":"Wau","r":3,"cap":0,"mega":0,"pop":127384,"lon":27.99,"lat":7.7},{"n":"Dongola","r":3,"cap":0,"mega":0,"pop":26404,"lon":30.483,"lat":19.167},{"n":"Kassala","r":3,"cap":0,"mega":0,"pop":401477,"lon":36.39,"lat":15.46},{"n":"Tromso","r":3,"cap":0,"mega":0,"pop":52436,"lon":18.992,"lat":69.635},{"n":"Trondheim","r":3,"cap":0,"mega":0,"pop":147139,"lon":10.417,"lat":63.417},{"n":"Bergen","r":3,"cap":0,"mega":0,"pop":213585,"lon":5.325,"lat":60.391},{"n":"Islamabad","r":3,"cap":1,"mega":1,"pop":780000,"lon":73.081,"lat":33.689},{"n":"Multan","r":3,"cap":0,"mega":1,"pop":1522000,"lon":71.453,"lat":30.202},{"n":"Hyderabad","r":3,"cap":0,"mega":1,"pop":1459000,"lon":68.373,"lat":25.382},{"n":"Peshawar","r":3,"cap":0,"mega":1,"pop":1303000,"lon":71.533,"lat":34.007},{"n":"Kathmandu","r":3,"cap":1,"mega":1,"pop":895000,"lon":85.315,"lat":27.719},{"n":"Nacala","r":3,"cap":0,"mega":0,"pop":224795,"lon":40.715,"lat":-14.519},{"n":"Bloemfontein","r":3,"cap":1,"mega":0,"pop":463064,"lon":26.23,"lat":-29.12},{"n":"Pretoria","r":3,"cap":1,"mega":1,"pop":1338000,"lon":28.227,"lat":-25.705},{"n":"Port Moresby","r":3,"cap":1,"mega":0,"pop":283733,"lon":147.193,"lat":-9.465},{"n":"Honiara","r":3,"cap":1,"mega":0,"pop":76328,"lon":159.95,"lat":-9.438},{"n":"Panama City","r":3,"cap":1,"mega":1,"pop":1281000,"lon":-79.535,"lat":8.97},{"n":"Fez","r":3,"cap":0,"mega":1,"pop":1002000,"lon":-5.002,"lat":34.057},{"n":"Rabat","r":3,"cap":1,"mega":1,"pop":1705000,"lon":-6.836,"lat":34.025},{"n":"Marrakesh","r":3,"cap":0,"mega":1,"pop":872000,"lon":-8.002,"lat":31.632},{"n":"Chisinau","r":3,"cap":1,"mega":0,"pop":688134,"lon":28.858,"lat":47.005},{"n":"Beira","r":3,"cap":0,"mega":0,"pop":530604,"lon":34.87,"lat":-19.82},{"n":"Port Elizabeth","r":3,"cap":0,"mega":1,"pop":1021000,"lon":25.598,"lat":-33.968},{"n":"Maputo","r":3,"cap":1,"mega":1,"pop":1446000,"lon":32.587,"lat":-25.953},{"n":"Tomsk","r":3,"cap":0,"mega":0,"pop":535479,"lon":84.975,"lat":56.495},{"n":"Anadyr","r":3,"cap":0,"mega":0,"pop":10332,"lon":177.475,"lat":64.737},{"n":"Murmansk","r":3,"cap":0,"mega":0,"pop":319263,"lon":33.1,"lat":68.97},{"n":"Archangel","r":3,"cap":0,"mega":0,"pop":356051,"lon":40.545,"lat":64.575},{"n":"Nizhny Novgorod","r":3,"cap":0,"mega":1,"pop":1278000,"lon":43.998,"lat":56.335},{"n":"Volgograd","r":3,"cap":0,"mega":1,"pop":984000,"lon":44.498,"lat":48.712},{"n":"Ufa","r":3,"cap":0,"mega":1,"pop":1018000,"lon":56.038,"lat":54.792},{"n":"Yekaterinburg","r":3,"cap":0,"mega":1,"pop":1313000,"lon":60.598,"lat":56.852},{"n":"Samara","r":3,"cap":0,"mega":1,"pop":1137000,"lon":50.149,"lat":53.197},{"n":"Kazan","r":3,"cap":0,"mega":1,"pop":1115000,"lon":49.124,"lat":55.752},{"n":"Surgut","r":3,"cap":0,"mega":0,"pop":400000,"lon":73.425,"lat":61.26},{"n":"Barnaul","r":3,"cap":0,"mega":0,"pop":599579,"lon":83.745,"lat":53.355},{"n":"Novosibirsk","r":3,"cap":0,"mega":1,"pop":1389000,"lon":82.958,"lat":55.032},{"n":"Mogadishu","r":3,"cap":1,"mega":1,"pop":1100000,"lon":45.365,"lat":2.069},{"n":"Muscat","r":3,"cap":1,"mega":0,"pop":734697,"lon":58.378,"lat":23.585},{"n":"Colombo","r":3,"cap":1,"mega":0,"pop":217000,"lon":79.858,"lat":6.932},{"n":"Cebu","r":3,"cap":0,"mega":1,"pop":815000,"lon":123.898,"lat":10.322},{"n":"Iloilo","r":3,"cap":0,"mega":0,"pop":387681,"lon":122.545,"lat":10.705},{"n":"Davao","r":3,"cap":0,"mega":1,"pop":1402000,"lon":125.628,"lat":7.112},{"n":"Bratsk","r":3,"cap":0,"mega":0,"pop":246348,"lon":101.615,"lat":56.157},{"n":"Irkutsk","r":3,"cap":0,"mega":0,"pop":586695,"lon":104.284,"lat":52.285},{"n":"Krasnoyarsk","r":3,"cap":0,"mega":1,"pop":925000,"lon":92.864,"lat":56.016},{"n":"Dikson","r":3,"cap":0,"mega":0,"pop":1113,"lon":80.545,"lat":73.507},{"n":"Chita","r":3,"cap":0,"mega":0,"pop":308500,"lon":113.465,"lat":52.055},{"n":"Vladivostok","r":3,"cap":0,"mega":0,"pop":587022,"lon":131.91,"lat":43.13},{"n":"Nizhneyansk","r":3,"cap":0,"mega":0,"pop":400,"lon":136.067,"lat":71.433},{"n":"Yakutsk","r":3,"cap":0,"mega":0,"pop":235600,"lon":129.735,"lat":62.035},{"n":"Tiksi","r":3,"cap":0,"mega":0,"pop":5700,"lon":128.835,"lat":71.627},{"n":"Magadan","r":3,"cap":0,"mega":0,"pop":95282,"lon":150.81,"lat":59.575},{"n":"Tijuana","r":3,"cap":0,"mega":1,"pop":1553000,"lon":-117.082,"lat":32.502},{"n":"Chihuahua","r":3,"cap":0,"mega":1,"pop":793000,"lon":-106.087,"lat":28.647},{"n":"Mazatlan","r":3,"cap":0,"mega":0,"pop":368204,"lon":-106.42,"lat":23.221},{"n":"Tampico","r":3,"cap":0,"mega":0,"pop":859419,"lon":-97.87,"lat":22.3},{"n":"Acapulco","r":3,"cap":0,"mega":0,"pop":715584,"lon":-99.916,"lat":16.85},{"n":"Veracruz","r":3,"cap":0,"mega":0,"pop":578963,"lon":-96.16,"lat":19.177},{"n":"Tuxtla Gutierrez","r":3,"cap":0,"mega":0,"pop":481128,"lon":-93.15,"lat":16.75},{"n":"Cancun","r":3,"cap":0,"mega":0,"pop":542043,"lon":-86.83,"lat":21.17},{"n":"Merida","r":3,"cap":0,"mega":1,"pop":965000,"lon":-89.619,"lat":20.969},{"n":"Enugu","r":3,"cap":0,"mega":0,"pop":688862,"lon":7.5,"lat":6.45},{"n":"Sokoto","r":3,"cap":0,"mega":0,"pop":732178,"lon":5.24,"lat":13.06},{"n":"Perm","r":3,"cap":0,"mega":1,"pop":997000,"lon":56.248,"lat":58.002},{"n":"Erdenet","r":3,"cap":0,"mega":0,"pop":79647,"lon":104.118,"lat":49.053},{"n":"Ulaanbaatar","r":3,"cap":1,"mega":1,"pop":885000,"lon":106.915,"lat":47.919},{"n":"Mbeya","r":3,"cap":0,"mega":0,"pop":291649,"lon":33.43,"lat":-8.89},{"n":"Windhoek","r":3,"cap":1,"mega":0,"pop":268132,"lon":17.084,"lat":-22.57},{"n":"Grootfontein","r":3,"cap":0,"mega":0,"pop":24099,"lon":18.117,"lat":-19.567},{"n":"Zanzibar","r":3,"cap":0,"mega":0,"pop":403658,"lon":39.2,"lat":-6.16},{"n":"Valencia","r":3,"cap":0,"mega":1,"pop":808000,"lon":-0.402,"lat":39.487},{"n":"Palana","r":3,"cap":0,"mega":0,"pop":3671,"lon":159.95,"lat":59.084},{"n":"Petropavlovsk Kamchatskiy","r":3,"cap":0,"mega":0,"pop":187282,"lon":158.623,"lat":53.062},{"n":"Abuja","r":3,"cap":1,"mega":1,"pop":1576000,"lon":7.49,"lat":9.055},{"n":"Padang","r":3,"cap":0,"mega":1,"pop":855000,"lon":100.358,"lat":-0.958},{"n":"Bissau","r":3,"cap":1,"mega":0,"pop":403339,"lon":-15.598,"lat":11.865},{"n":"Palermo","r":3,"cap":0,"mega":1,"pop":863000,"lon":13.348,"lat":38.127},{"n":"Amman","r":3,"cap":1,"mega":1,"pop":1060000,"lon":35.931,"lat":31.952},{"n":"Vilnius","r":3,"cap":1,"mega":0,"pop":542366,"lon":25.317,"lat":54.683},{"n":"Riga","r":3,"cap":1,"mega":0,"pop":742572,"lon":24.1,"lat":56.95},{"n":"Bishkek","r":3,"cap":1,"mega":1,"pop":837000,"lon":74.583,"lat":42.875},{"n":"Jiayuguan","r":3,"cap":0,"mega":0,"pop":148279,"lon":98.3,"lat":39.82},{"n":"Xining","r":3,"cap":0,"mega":1,"pop":1048000,"lon":101.768,"lat":36.622},{"n":"Guilin","r":3,"cap":0,"mega":1,"pop":987000,"lon":110.278,"lat":25.282},{"n":"Huainan","r":3,"cap":0,"mega":1,"pop":1451000,"lon":116.978,"lat":32.632},{"n":"Shantou","r":3,"cap":0,"mega":1,"pop":1601000,"lon":116.668,"lat":23.372},{"n":"Tarakan","r":3,"cap":0,"mega":0,"pop":193069,"lon":117.633,"lat":3.3},{"n":"Mombasa","r":3,"cap":0,"mega":1,"pop":882000,"lon":39.688,"lat":-4.038},{"n":"Maseru","r":3,"cap":1,"mega":0,"pop":361324,"lon":27.483,"lat":-29.317},{"n":"Antananarivo","r":3,"cap":1,"mega":1,"pop":1697000,"lon":47.515,"lat":-18.915},{"n":"Semarang","r":3,"cap":0,"mega":1,"pop":1396000,"lon":110.418,"lat":-6.965},{"n":"Palembang","r":3,"cap":0,"mega":1,"pop":1749000,"lon":104.748,"lat":-2.978},{"n":"Bandjarmasin","r":3,"cap":0,"mega":0,"pop":603576,"lon":114.58,"lat":-3.33},{"n":"Makassar","r":3,"cap":0,"mega":1,"pop":1262000,"lon":119.43,"lat":-5.138},{"n":"Lyon","r":3,"cap":0,"mega":1,"pop":1423000,"lon":4.828,"lat":45.772},{"n":"Quito","r":3,"cap":1,"mega":1,"pop":1701000,"lon":-78.502,"lat":-0.213},{"n":"San Jose","r":3,"cap":1,"mega":1,"pop":1284000,"lon":-84.079,"lat":9.93},{"n":"San Salvador","r":3,"cap":1,"mega":1,"pop":1433000,"lon":-89.216,"lat":13.703},{"n":"Kingston","r":3,"cap":1,"mega":0,"pop":937700,"lon":-76.767,"lat":17.977},{"n":"Cartagena","r":3,"cap":0,"mega":1,"pop":887000,"lon":-75.516,"lat":10.402},{"n":"Mitu","r":3,"cap":0,"mega":0,"pop":5917,"lon":-70.174,"lat":1.198},{"n":"Bumba","r":3,"cap":0,"mega":0,"pop":160539,"lon":22.46,"lat":2.19},{"n":"Ndjamena","r":3,"cap":1,"mega":1,"pop":989000,"lon":15.047,"lat":12.115},{"n":"Abeche","r":3,"cap":0,"mega":0,"pop":158317,"lon":20.83,"lat":13.84},{"n":"Malabo","r":3,"cap":1,"mega":0,"pop":155963,"lon":8.783,"lat":3.75},{"n":"Luxor","r":3,"cap":0,"mega":0,"pop":609248,"lon":32.65,"lat":25.7},{"n":"Asmara","r":3,"cap":1,"mega":0,"pop":620802,"lon":38.933,"lat":15.333},{"n":"Zagreb","r":3,"cap":1,"mega":0,"pop":722526,"lon":16.0,"lat":45.8},{"n":"Tallinn","r":3,"cap":1,"mega":0,"pop":394024,"lon":24.728,"lat":59.434},{"n":"Lhasa","r":3,"cap":0,"mega":0,"pop":219599,"lon":91.1,"lat":29.645},{"n":"Hami","r":3,"cap":0,"mega":0,"pop":580000,"lon":93.515,"lat":42.827},{"n":"Hotan","r":3,"cap":0,"mega":0,"pop":408894,"lon":79.927,"lat":37.1},{"n":"Kashgar","r":3,"cap":0,"mega":0,"pop":506640,"lon":75.97,"lat":39.476},{"n":"Yinchuan","r":3,"cap":0,"mega":1,"pop":991000,"lon":106.271,"lat":38.47},{"n":"Pingxiang","r":3,"cap":0,"mega":1,"pop":961000,"lon":113.848,"lat":27.622},{"n":"Nagasaki","r":3,"cap":0,"mega":0,"pop":435455,"lon":129.885,"lat":32.765},{"n":"Qiqihar","r":3,"cap":0,"mega":1,"pop":1641000,"lon":123.988,"lat":47.347},{"n":"Kikwit","r":3,"cap":0,"mega":0,"pop":637736,"lon":18.85,"lat":-5.03},{"n":"Matadi","r":3,"cap":0,"mega":0,"pop":245862,"lon":13.45,"lat":-5.817},{"n":"Kolwezi","r":3,"cap":0,"mega":0,"pop":418000,"lon":25.472,"lat":-10.717},{"n":"Lubumbashi","r":3,"cap":0,"mega":1,"pop":1352000,"lon":27.478,"lat":-11.678},{"n":"Lilongwe","r":3,"cap":1,"mega":0,"pop":646750,"lon":33.783,"lat":-13.983},{"n":"Guatemala","r":3,"cap":1,"mega":1,"pop":1024000,"lon":-90.529,"lat":14.623},{"n":"Cayenne","r":3,"cap":0,"mega":0,"pop":61550,"lon":-52.33,"lat":4.933},{"n":"Libreville","r":3,"cap":1,"mega":0,"pop":578156,"lon":9.458,"lat":0.385},{"n":"Vishakhapatnam","r":3,"cap":0,"mega":1,"pop":1529000,"lon":83.22,"lat":17.683},{"n":"Suva","r":3,"cap":1,"mega":0,"pop":175399,"lon":178.442,"lat":-18.133},{"n":"Port-Gentil","r":3,"cap":0,"mega":0,"pop":116836,"lon":8.78,"lat":-0.72},{"n":"Timbuktu","r":3,"cap":0,"mega":0,"pop":68872,"lon":-3.017,"lat":16.767},{"n":"Punta Arenas","r":3,"cap":0,"mega":0,"pop":117430,"lon":-70.94,"lat":-53.165},{"n":"Iquique","r":3,"cap":0,"mega":0,"pop":227499,"lon":-70.13,"lat":-20.25},{"n":"Antofagasta","r":3,"cap":0,"mega":0,"pop":309832,"lon":-70.4,"lat":-23.65},{"n":"Valparaiso","r":3,"cap":0,"mega":1,"pop":854000,"lon":-71.617,"lat":-33.048},{"n":"Valdivia","r":3,"cap":0,"mega":0,"pop":159599,"lon":-73.233,"lat":-39.822},{"n":"Concepcion","r":3,"cap":0,"mega":0,"pop":889725,"lon":-73.05,"lat":-36.83},{"n":"Puerto Montt","r":3,"cap":0,"mega":0,"pop":174629,"lon":-72.93,"lat":-41.47},{"n":"Nuuk","r":3,"cap":0,"mega":0,"pop":14798,"lon":-51.733,"lat":64.198},{"n":"Nouakchott","r":3,"cap":1,"mega":0,"pop":742144,"lon":-15.975,"lat":18.086},{"n":"Bamako","r":3,"cap":1,"mega":1,"pop":1494000,"lon":-8.002,"lat":12.652},{"n":"Atar","r":3,"cap":0,"mega":0,"pop":44265,"lon":-13.05,"lat":20.517},{"n":"Djenne","r":3,"cap":0,"mega":0,"pop":32944,"lon":-4.55,"lat":13.9},{"n":"Sabha","r":3,"cap":0,"mega":0,"pop":100249,"lon":14.433,"lat":27.033},{"n":"Banghazi","r":3,"cap":0,"mega":1,"pop":1180000,"lon":20.065,"lat":32.119},{"n":"Thessaloniki","r":3,"cap":0,"mega":1,"pop":828000,"lon":22.883,"lat":40.698},{"n":"Beirut","r":3,"cap":1,"mega":1,"pop":1846000,"lon":35.508,"lat":33.874},{"n":"Tbilisi","r":3,"cap":1,"mega":1,"pop":1100000,"lon":44.789,"lat":41.727},{"n":"Gonder","r":3,"cap":0,"mega":0,"pop":156230,"lon":37.46,"lat":12.61},{"n":"Astana","r":3,"cap":1,"mega":0,"pop":345604,"lon":71.428,"lat":51.181},{"n":"Qaraghandy","r":3,"cap":0,"mega":0,"pop":451800,"lon":73.115,"lat":49.885},{"n":"Almaty","r":3,"cap":0,"mega":1,"pop":1209000,"lon":76.913,"lat":43.327},{"n":"Isfahan","r":3,"cap":0,"mega":1,"pop":1628000,"lon":51.698,"lat":32.702},{"n":"Shiraz","r":3,"cap":0,"mega":1,"pop":1240000,"lon":52.568,"lat":29.632},{"n":"Amritsar","r":3,"cap":0,"mega":1,"pop":1212000,"lon":74.868,"lat":31.642},{"n":"Varanasi","r":3,"cap":0,"mega":1,"pop":1352000,"lon":82.998,"lat":25.332},{"n":"Asansol","r":3,"cap":0,"mega":1,"pop":1328000,"lon":86.981,"lat":23.685},{"n":"Bhilai","r":3,"cap":0,"mega":1,"pop":1097000,"lon":81.372,"lat":21.211},{"n":"Bhopal","r":3,"cap":0,"mega":1,"pop":1727000,"lon":77.408,"lat":23.252},{"n":"Madurai","r":3,"cap":0,"mega":1,"pop":1294000,"lon":78.118,"lat":9.922},{"n":"Coimbatore","r":3,"cap":0,"mega":1,"pop":1696000,"lon":76.948,"lat":11.002},{"n":"Vientiane","r":3,"cap":1,"mega":0,"pop":754000,"lon":102.6,"lat":17.967},{"n":"Brazzaville","r":3,"cap":1,"mega":1,"pop":1355000,"lon":15.283,"lat":-4.257},{"n":"Conakry","r":3,"cap":1,"mega":1,"pop":1494000,"lon":-13.682,"lat":9.533},{"n":"Yamoussoukro","r":3,"cap":1,"mega":0,"pop":206499,"lon":-5.276,"lat":6.818},{"n":"Cruzeiro do Sul","r":3,"cap":0,"mega":0,"pop":56862,"lon":-72.67,"lat":-7.63},{"n":"Leticia","r":3,"cap":0,"mega":0,"pop":59575,"lon":-69.955,"lat":-4.201},{"n":"Manaus","r":3,"cap":0,"mega":1,"pop":1753000,"lon":-60.002,"lat":-3.098},{"n":"Caxias","r":3,"cap":0,"mega":0,"pop":134640,"lon":-43.35,"lat":-4.833},{"n":"Santarem","r":3,"cap":0,"mega":0,"pop":230428,"lon":-54.7,"lat":-2.433},{"n":"Maraba","r":3,"cap":0,"mega":0,"pop":166182,"lon":-49.116,"lat":-5.35},{"n":"Vilhena","r":3,"cap":0,"mega":0,"pop":63231,"lon":-60.117,"lat":-12.717},{"n":"Ji-Parana","r":3,"cap":0,"mega":0,"pop":65016,"lon":-61.967,"lat":-10.833},{"n":"Campo Grande","r":3,"cap":0,"mega":1,"pop":778000,"lon":-54.619,"lat":-20.448},{"n":"Florianopolis","r":3,"cap":0,"mega":1,"pop":1023000,"lon":-48.522,"lat":-27.578},{"n":"Feira de Santana","r":3,"cap":0,"mega":0,"pop":481911,"lon":-38.97,"lat":-12.25},{"n":"Winnipeg","r":3,"cap":0,"mega":0,"pop":632063,"lon":-97.166,"lat":49.883},{"n":"Churchill","r":3,"cap":0,"mega":0,"pop":1000,"lon":-94.166,"lat":58.766},{"n":"Regina","r":3,"cap":0,"mega":0,"pop":176183,"lon":-104.617,"lat":50.45},{"n":"Saskatoon","r":3,"cap":0,"mega":0,"pop":198958,"lon":-106.67,"lat":52.17},{"n":"Calgary","r":3,"cap":0,"mega":1,"pop":1110000,"lon":-114.082,"lat":51.085},{"n":"Prince Rupert","r":3,"cap":0,"mega":0,"pop":14708,"lon":-130.33,"lat":54.317},{"n":"Victoria","r":3,"cap":0,"mega":0,"pop":289625,"lon":-123.35,"lat":48.433},{"n":"Arctic Bay","r":3,"cap":0,"mega":0,"pop":604,"lon":-85.167,"lat":73.033},{"n":"Resolute","r":3,"cap":0,"mega":0,"pop":250,"lon":-94.9,"lat":74.683},{"n":"Repulse Bay","r":3,"cap":0,"mega":0,"pop":1000,"lon":-86.283,"lat":66.529},{"n":"Yellowknife","r":3,"cap":0,"mega":0,"pop":19234,"lon":-114.397,"lat":62.442},{"n":"Fort Good Hope","r":3,"cap":0,"mega":0,"pop":597,"lon":-128.633,"lat":66.267},{"n":"Whitehorse","r":3,"cap":0,"mega":0,"pop":23276,"lon":-135.05,"lat":60.717},{"n":"Boa Vista","r":3,"cap":0,"mega":0,"pop":235150,"lon":-60.666,"lat":2.816},{"n":"Macapa","r":3,"cap":0,"mega":0,"pop":499166,"lon":-51.05,"lat":0.033},{"n":"Ottawa","r":3,"cap":1,"mega":1,"pop":1145000,"lon":-75.702,"lat":45.419},{"n":"Fort Severn","r":3,"cap":0,"mega":0,"pop":125,"lon":-87.65,"lat":55.983},{"n":"Thunder Bay","r":3,"cap":0,"mega":0,"pop":99334,"lon":-89.275,"lat":48.446},{"n":"Quebec","r":3,"cap":0,"mega":0,"pop":624177,"lon":-71.246,"lat":46.84},{"n":"Halifax","r":3,"cap":0,"mega":0,"pop":359111,"lon":-63.6,"lat":44.65},{"n":"St. John's","r":3,"cap":0,"mega":0,"pop":131469,"lon":-52.681,"lat":47.585},{"n":"Nain","r":3,"cap":0,"mega":0,"pop":1151,"lon":-61.686,"lat":56.547},{"n":"Charlottetown","r":3,"cap":0,"mega":0,"pop":42402,"lon":-63.131,"lat":46.249},{"n":"Ndele","r":3,"cap":0,"mega":0,"pop":11764,"lon":20.653,"lat":8.409},{"n":"Belgrade","r":3,"cap":1,"mega":1,"pop":1099000,"lon":20.466,"lat":44.821},{"n":"Obo","r":3,"cap":0,"mega":0,"pop":12887,"lon":26.5,"lat":5.4},{"n":"Bandar Seri Begawan","r":3,"cap":1,"mega":0,"pop":296500,"lon":114.933,"lat":4.883},{"n":"Puerto Deseado","r":3,"cap":0,"mega":0,"pop":3305,"lon":-65.9,"lat":-47.75},{"n":"Rio Gallegos","r":3,"cap":0,"mega":0,"pop":85700,"lon":-69.217,"lat":-51.633},{"n":"Comodoro Rivadavia","r":3,"cap":0,"mega":0,"pop":140850,"lon":-67.5,"lat":-45.87},{"n":"Mendoza","r":3,"cap":0,"mega":1,"pop":893000,"lon":-68.819,"lat":-32.881},{"n":"Sucre","r":3,"cap":1,"mega":0,"pop":224838,"lon":-65.26,"lat":-19.041},{"n":"Riberalta","r":3,"cap":0,"mega":0,"pop":74014,"lon":-66.1,"lat":-10.983},{"n":"Bahia Blanca","r":3,"cap":0,"mega":0,"pop":281536,"lon":-62.265,"lat":-38.74},{"n":"Mar del Plata","r":3,"cap":0,"mega":0,"pop":555897,"lon":-57.58,"lat":-38.0},{"n":"Cordoba","r":3,"cap":0,"mega":1,"pop":1452000,"lon":-64.184,"lat":-31.398},{"n":"Posadas","r":3,"cap":0,"mega":0,"pop":357119,"lon":-55.885,"lat":-27.358},{"n":"Belmopan","r":3,"cap":1,"mega":0,"pop":15220,"lon":-88.767,"lat":17.252},{"n":"Bangui","r":3,"cap":1,"mega":0,"pop":831925,"lon":18.558,"lat":4.367},{"n":"Maroua","r":3,"cap":0,"mega":0,"pop":319941,"lon":14.325,"lat":10.596},{"n":"Yaounde","r":3,"cap":1,"mega":1,"pop":1611000,"lon":11.515,"lat":3.869},{"n":"Tirana","r":3,"cap":1,"mega":0,"pop":895350,"lon":19.819,"lat":41.328},{"n":"Yerevan","r":3,"cap":1,"mega":1,"pop":1102000,"lon":44.512,"lat":40.183},{"n":"Baku","r":3,"cap":1,"mega":1,"pop":2122300,"lon":49.86,"lat":40.397},{"n":"Kandahar","r":3,"cap":0,"mega":0,"pop":715542,"lon":65.695,"lat":31.61},{"n":"Phnom Penh","r":3,"cap":1,"mega":1,"pop":1466000,"lon":104.915,"lat":11.552},{"n":"Menongue","r":3,"cap":0,"mega":0,"pop":13030,"lon":17.7,"lat":-14.667},{"n":"Huambo","r":3,"cap":0,"mega":1,"pop":1100000,"lon":15.758,"lat":-12.748},{"n":"La Paz","r":3,"cap":1,"mega":1,"pop":1590000,"lon":-68.152,"lat":-16.496},{"n":"Santa Cruz","r":3,"cap":0,"mega":1,"pop":2102998,"lon":-63.228,"lat":-17.752},{"n":"Oran","r":3,"cap":0,"mega":1,"pop":798000,"lon":-0.622,"lat":35.712},{"n":"Cotonou","r":3,"cap":1,"mega":1,"pop":762000,"lon":2.404,"lat":6.363},{"n":"Tamanrasset","r":3,"cap":0,"mega":0,"pop":76000,"lon":5.523,"lat":22.785},{"n":"Ghardaia","r":3,"cap":0,"mega":0,"pop":125480,"lon":3.67,"lat":32.49},{"n":"Sofia","r":3,"cap":1,"mega":1,"pop":1185000,"lon":23.315,"lat":42.685},{"n":"Minsk","r":3,"cap":1,"mega":1,"pop":1805000,"lon":27.565,"lat":53.902},{"n":"Thimphu","r":3,"cap":1,"mega":0,"pop":98676,"lon":89.639,"lat":27.473},{"n":"Gaborone","r":3,"cap":1,"mega":0,"pop":208411,"lon":25.912,"lat":-24.646},{"n":"Darwin","r":3,"cap":0,"mega":0,"pop":93080,"lon":130.85,"lat":-12.425},{"n":"Alice Springs","r":3,"cap":0,"mega":0,"pop":27710,"lon":133.88,"lat":-23.701},{"n":"Canberra","r":3,"cap":1,"mega":0,"pop":327700,"lon":149.129,"lat":-35.283},{"n":"Newcastle","r":3,"cap":0,"mega":0,"pop":540796,"lon":151.77,"lat":-32.93},{"n":"Adelaide","r":3,"cap":0,"mega":1,"pop":1145000,"lon":138.598,"lat":-34.933},{"n":"Townsville","r":3,"cap":0,"mega":0,"pop":138954,"lon":146.77,"lat":-19.25},{"n":"Brisbane","r":3,"cap":0,"mega":1,"pop":1860000,"lon":153.033,"lat":-27.453},{"n":"Hobart","r":3,"cap":0,"mega":0,"pop":80870,"lon":147.295,"lat":-42.85},{"n":"Ouagadougou","r":3,"cap":1,"mega":1,"pop":1149000,"lon":-1.527,"lat":12.372},{"n":"Sarajevo","r":3,"cap":1,"mega":0,"pop":696731,"lon":18.383,"lat":43.85},{"n":"Naypyidaw","r":3,"cap":1,"mega":1,"pop":930000,"lon":96.117,"lat":19.769},{"n":"San Juan","r":3,"cap":0,"mega":1,"pop":2478905,"lon":-66.13,"lat":18.44},{"n":"Stanley","r":3,"cap":0,"mega":0,"pop":2213,"lon":-57.85,"lat":-51.7},{"n":"Hamilton","r":3,"cap":1,"mega":0,"pop":52320,"lon":-64.784,"lat":32.294},{"n":"Nukualofa","r":3,"cap":1,"mega":0,"pop":42620,"lon":-175.221,"lat":-21.139},{"n":"Hargeysa","r":3,"cap":1,"mega":0,"pop":477876,"lon":44.065,"lat":9.56},{"n":"Victoria","r":3,"cap":1,"mega":0,"pop":33576,"lon":55.45,"lat":-4.617},{"n":"Sao Tome","r":3,"cap":1,"mega":0,"pop":88219,"lon":6.73,"lat":0.337},{"n":"Apia","r":3,"cap":1,"mega":0,"pop":61916,"lon":-171.769,"lat":-13.836},{"n":"Valletta","r":3,"cap":1,"mega":0,"pop":368250,"lon":14.515,"lat":35.9},{"n":"Male","r":3,"cap":1,"mega":0,"pop":112927,"lon":73.509,"lat":4.172},{"n":"Jerusalem","r":3,"cap":1,"mega":0,"pop":1029300,"lon":35.207,"lat":31.778},{"n":"Praia","r":3,"cap":1,"mega":0,"pop":113364,"lon":-23.517,"lat":14.917},{"n":"Nassau","r":3,"cap":1,"mega":0,"pop":227940,"lon":-77.35,"lat":25.083},{"n":"Nicosia","r":3,"cap":1,"mega":0,"pop":224300,"lon":33.367,"lat":35.167},{"n":"Kaohsiung","r":3,"cap":0,"mega":1,"pop":2769072,"lon":120.267,"lat":22.633},{"n":"Wellington","r":3,"cap":1,"mega":0,"pop":393400,"lon":174.777,"lat":-41.292},{"n":"Christchurch","r":3,"cap":0,"mega":0,"pop":363200,"lon":172.63,"lat":-43.535},{"n":"Shenzhen","r":2,"cap":0,"mega":1,"pop":7581000,"lon":114.061,"lat":22.548},{"n":"Zibo","r":2,"cap":0,"mega":1,"pop":3061000,"lon":117.996,"lat":36.821},{"n":"Minneapolis","r":2,"cap":0,"mega":1,"pop":2616000,"lon":-93.254,"lat":44.982},{"n":"Honolulu","r":2,"cap":0,"mega":1,"pop":786000,"lon":-157.858,"lat":21.303},{"n":"Seattle","r":2,"cap":0,"mega":1,"pop":3074000,"lon":-122.315,"lat":47.6},{"n":"Phoenix","r":2,"cap":0,"mega":1,"pop":3551000,"lon":-112.067,"lat":33.448},{"n":"San Diego","r":2,"cap":0,"mega":1,"pop":2916000,"lon":-117.151,"lat":32.719},{"n":"St. Louis","r":2,"cap":0,"mega":1,"pop":2199000,"lon":-90.242,"lat":38.637},{"n":"New Orleans","r":2,"cap":0,"mega":1,"pop":785000,"lon":-90.088,"lat":29.969},{"n":"Dallas","r":2,"cap":0,"mega":1,"pop":4798000,"lon":-96.795,"lat":32.772},{"n":"Maracaibo","r":2,"cap":0,"mega":1,"pop":2072000,"lon":-71.662,"lat":10.732},{"n":"Boston","r":2,"cap":0,"mega":1,"pop":4467000,"lon":-71.072,"lat":42.332},{"n":"Tampa","r":2,"cap":0,"mega":1,"pop":2314000,"lon":-82.461,"lat":27.949},{"n":"Philadelphia","r":2,"cap":0,"mega":1,"pop":5492000,"lon":-75.18,"lat":39.946},{"n":"Detroit","r":2,"cap":0,"mega":1,"pop":4101000,"lon":-83.051,"lat":42.334},{"n":"Anchorage","r":2,"cap":0,"mega":0,"pop":260283,"lon":-149.887,"lat":61.214},{"n":"Hanoi","r":2,"cap":1,"mega":1,"pop":4378000,"lon":105.848,"lat":21.035},{"n":"Ho Chi Minh City","r":2,"cap":0,"mega":1,"pop":5314000,"lon":106.703,"lat":10.762},{"n":"Ankara","r":2,"cap":1,"mega":1,"pop":3716000,"lon":32.862,"lat":39.929},{"n":"Budapest","r":2,"cap":1,"mega":1,"pop":1679000,"lon":19.081,"lat":47.502},{"n":"Sanaa","r":2,"cap":1,"mega":1,"pop":2008000,"lon":44.205,"lat":15.357},{"n":"Barcelona","r":2,"cap":0,"mega":1,"pop":4920000,"lon":2.181,"lat":41.385},{"n":"Bucharest","r":2,"cap":1,"mega":1,"pop":1942000,"lon":26.098,"lat":44.435},{"n":"Aleppo","r":2,"cap":0,"mega":1,"pop":2738000,"lon":37.168,"lat":36.232},{"n":"Damascus","r":2,"cap":1,"mega":1,"pop":2466000,"lon":36.298,"lat":33.502},{"n":"Zurich","r":2,"cap":0,"mega":1,"pop":1108000,"lon":8.548,"lat":47.382},{"n":"Lisbon","r":2,"cap":1,"mega":1,"pop":2812000,"lon":-9.147,"lat":38.725},{"n":"Khartoum","r":2,"cap":1,"mega":1,"pop":4754000,"lon":32.532,"lat":15.59},{"n":"Jeddah","r":2,"cap":0,"mega":1,"pop":3012000,"lon":39.217,"lat":21.519},{"n":"Makkah","r":2,"cap":0,"mega":1,"pop":1385000,"lon":39.818,"lat":21.432},{"n":"Oslo","r":2,"cap":1,"mega":1,"pop":835000,"lon":10.748,"lat":59.919},{"n":"Lahore","r":2,"cap":0,"mega":1,"pop":6577000,"lon":74.348,"lat":31.562},{"n":"Karachi","r":2,"cap":0,"mega":1,"pop":12130000,"lon":66.988,"lat":24.872},{"n":"Durban","r":2,"cap":0,"mega":1,"pop":2729000,"lon":31.011,"lat":-29.861},{"n":"St. Petersburg","r":2,"cap":0,"mega":1,"pop":4553000,"lon":30.314,"lat":59.941},{"n":"Guadalajara","r":2,"cap":0,"mega":1,"pop":4198000,"lon":-103.332,"lat":20.672},{"n":"Puebla","r":2,"cap":0,"mega":1,"pop":2195000,"lon":-98.204,"lat":19.033},{"n":"Kano","r":2,"cap":0,"mega":1,"pop":3140000,"lon":8.518,"lat":12.002},{"n":"Warsaw","r":2,"cap":1,"mega":1,"pop":1707000,"lon":21.005,"lat":52.231},{"n":"Pyongyang","r":2,"cap":1,"mega":1,"pop":3300000,"lon":125.753,"lat":39.021},{"n":"Dar es Salaam","r":2,"cap":1,"mega":1,"pop":2930000,"lon":39.266,"lat":-6.798},{"n":"Medan","r":2,"cap":0,"mega":1,"pop":2115000,"lon":98.648,"lat":3.582},{"n":"Dublin","r":2,"cap":1,"mega":1,"pop":1059000,"lon":-6.257,"lat":53.347},{"n":"Monrovia","r":2,"cap":1,"mega":1,"pop":1041000,"lon":-10.8,"lat":6.315},{"n":"Naples","r":2,"cap":0,"mega":1,"pop":2250000,"lon":14.243,"lat":40.842},{"n":"Milan","r":2,"cap":0,"mega":1,"pop":2945000,"lon":9.203,"lat":45.472},{"n":"Kuala Lumpur","r":2,"cap":1,"mega":1,"pop":1448000,"lon":101.689,"lat":3.14},{"n":"Lanzhou","r":2,"cap":0,"mega":1,"pop":2561000,"lon":103.79,"lat":36.058},{"n":"Nanning","r":2,"cap":0,"mega":1,"pop":2167000,"lon":108.318,"lat":22.822},{"n":"Guiyang","r":2,"cap":0,"mega":1,"pop":3662000,"lon":106.718,"lat":26.582},{"n":"Chongqing","r":2,"cap":0,"mega":1,"pop":6461000,"lon":106.593,"lat":29.567},{"n":"Fuzhou","r":2,"cap":0,"mega":1,"pop":2606000,"lon":119.298,"lat":26.082},{"n":"Guangzhou","r":2,"cap":0,"mega":1,"pop":8829000,"lon":113.262,"lat":23.125},{"n":"Dongguan","r":2,"cap":0,"mega":1,"pop":4528000,"lon":113.75,"lat":23.022},{"n":"Bandung","r":2,"cap":0,"mega":1,"pop":2394000,"lon":107.568,"lat":-6.948},{"n":"Surabaya","r":2,"cap":0,"mega":1,"pop":2845000,"lon":112.749,"lat":-7.247},{"n":"Guayaquil","r":2,"cap":0,"mega":1,"pop":2514000,"lon":-79.922,"lat":-2.218},{"n":"Medellin","r":2,"cap":0,"mega":1,"pop":3297000,"lon":-75.577,"lat":6.277},{"n":"Cali","r":2,"cap":0,"mega":1,"pop":2254000,"lon":-76.502,"lat":3.402},{"n":"Havana","r":2,"cap":1,"mega":1,"pop":2174000,"lon":-82.366,"lat":23.134},{"n":"Alexandria","r":2,"cap":0,"mega":1,"pop":4165000,"lon":29.948,"lat":31.202},{"n":"Frankfurt","r":2,"cap":0,"mega":0,"pop":2895000,"lon":8.675,"lat":50.1},{"n":"Hamburg","r":2,"cap":0,"mega":1,"pop":1757000,"lon":9.998,"lat":53.552},{"n":"Munich","r":2,"cap":0,"mega":1,"pop":1275000,"lon":11.573,"lat":48.132},{"n":"Prague","r":2,"cap":1,"mega":1,"pop":1162000,"lon":14.423,"lat":50.087},{"n":"Kuwait City","r":2,"cap":1,"mega":1,"pop":2063000,"lon":47.976,"lat":29.372},{"n":"Xian","r":2,"cap":0,"mega":1,"pop":4009000,"lon":108.893,"lat":34.277},{"n":"Taiyuan","r":2,"cap":0,"mega":1,"pop":2913000,"lon":112.543,"lat":37.877},{"n":"Wuhan","r":2,"cap":0,"mega":1,"pop":7243000,"lon":114.268,"lat":30.582},{"n":"Changsha","r":2,"cap":0,"mega":1,"pop":2604000,"lon":112.968,"lat":28.202},{"n":"Kunming","r":2,"cap":0,"mega":1,"pop":2931000,"lon":102.697,"lat":25.043},{"n":"Zhengzhou","r":2,"cap":0,"mega":1,"pop":2636000,"lon":113.663,"lat":34.757},{"n":"Shenyeng","r":2,"cap":0,"mega":1,"pop":4787000,"lon":123.448,"lat":41.807},{"n":"Jinan","r":2,"cap":0,"mega":1,"pop":2798000,"lon":116.993,"lat":36.677},{"n":"Tianjin","r":2,"cap":0,"mega":1,"pop":7180000,"lon":117.197,"lat":39.083},{"n":"Nanchang","r":2,"cap":0,"mega":1,"pop":2350000,"lon":115.878,"lat":28.682},{"n":"Nanjing","r":2,"cap":0,"mega":1,"pop":3679000,"lon":118.778,"lat":32.052},{"n":"Hangzhou","r":2,"cap":0,"mega":1,"pop":3007000,"lon":120.168,"lat":30.252},{"n":"Hiroshima","r":2,"cap":0,"mega":1,"pop":2045000,"lon":132.441,"lat":34.39},{"n":"Changchun","r":2,"cap":0,"mega":1,"pop":3183000,"lon":125.338,"lat":43.867},{"n":"Baotou","r":2,"cap":0,"mega":1,"pop":2036000,"lon":109.82,"lat":40.654},{"n":"Harbin","r":2,"cap":0,"mega":1,"pop":3621000,"lon":126.648,"lat":45.752},{"n":"Sapporo","r":2,"cap":0,"mega":1,"pop":2544000,"lon":141.338,"lat":43.077},{"n":"Santo Domingo","r":2,"cap":1,"mega":1,"pop":2154000,"lon":-69.93,"lat":18.471},{"n":"Accra","r":2,"cap":1,"mega":1,"pop":2121000,"lon":-0.219,"lat":5.552},{"n":"Delhi","r":2,"cap":0,"mega":1,"pop":15926000,"lon":77.228,"lat":28.672},{"n":"Hyderabad","r":2,"cap":0,"mega":1,"pop":6376000,"lon":78.478,"lat":17.402},{"n":"Pune","r":2,"cap":0,"mega":1,"pop":4672000,"lon":73.852,"lat":18.516},{"n":"Nagpur","r":2,"cap":0,"mega":1,"pop":2454000,"lon":79.088,"lat":21.172},{"n":"Tripoli","r":2,"cap":1,"mega":1,"pop":2189000,"lon":13.18,"lat":32.892},{"n":"Tel Aviv-Yafo","r":2,"cap":0,"mega":1,"pop":3112000,"lon":34.768,"lat":32.082},{"n":"Helsinki","r":2,"cap":1,"mega":1,"pop":1115000,"lon":24.932,"lat":60.164},{"n":"Mashhad","r":2,"cap":0,"mega":1,"pop":2469000,"lon":59.568,"lat":36.272},{"n":"Jaipur","r":2,"cap":0,"mega":1,"pop":2917000,"lon":75.808,"lat":26.923},{"n":"Kanpur","r":2,"cap":0,"mega":1,"pop":3162000,"lon":80.318,"lat":26.462},{"n":"Patna","r":2,"cap":0,"mega":1,"pop":2158000,"lon":85.128,"lat":25.627},{"n":"Chennai","r":2,"cap":0,"mega":1,"pop":7163000,"lon":80.278,"lat":13.092},{"n":"Ahmedabad","r":2,"cap":0,"mega":1,"pop":5375000,"lon":72.578,"lat":23.032},{"n":"Surat","r":2,"cap":0,"mega":1,"pop":3842000,"lon":72.838,"lat":21.202},{"n":"Kobenhavn","r":2,"cap":1,"mega":1,"pop":1085000,"lon":12.562,"lat":55.681},{"n":"Abidjan","r":2,"cap":1,"mega":1,"pop":3802000,"lon":-4.02,"lat":5.323},{"n":"Belem","r":2,"cap":0,"mega":1,"pop":2167000,"lon":-48.482,"lat":-1.448},{"n":"Brasilia","r":2,"cap":1,"mega":1,"pop":3716996,"lon":-47.918,"lat":-15.781},{"n":"Porto Alegre","r":2,"cap":0,"mega":1,"pop":3917000,"lon":-51.202,"lat":-30.048},{"n":"Curitiba","r":2,"cap":0,"mega":1,"pop":3084000,"lon":-49.322,"lat":-25.418},{"n":"Fortaleza","r":2,"cap":0,"mega":1,"pop":3602319,"lon":-38.582,"lat":-3.748},{"n":"Salvador","r":2,"cap":0,"mega":1,"pop":3484000,"lon":-38.482,"lat":-12.968},{"n":"Edmonton","r":2,"cap":0,"mega":1,"pop":1058000,"lon":-113.502,"lat":53.552},{"n":"Montreal","r":2,"cap":0,"mega":1,"pop":3678000,"lon":-73.585,"lat":45.502},{"n":"Goiania","r":2,"cap":0,"mega":1,"pop":2022000,"lon":-49.302,"lat":-16.718},{"n":"Recife","r":2,"cap":0,"mega":1,"pop":3651000,"lon":-34.909,"lat":-8.061},{"n":"Brussels","r":2,"cap":1,"mega":1,"pop":1743000,"lon":4.331,"lat":50.835},{"n":"Dhaka","r":2,"cap":1,"mega":1,"pop":12797394,"lon":90.407,"lat":23.725},{"n":"Luanda","r":2,"cap":1,"mega":1,"pop":5172900,"lon":13.232,"lat":-8.836},{"n":"Algiers","r":2,"cap":1,"mega":1,"pop":3354000,"lon":3.049,"lat":36.765},{"n":"Chattogram","r":2,"cap":0,"mega":1,"pop":4529000,"lon":91.798,"lat":22.332},{"n":"Perth","r":2,"cap":0,"mega":1,"pop":1532000,"lon":115.838,"lat":-31.953},{"n":"Rangoon","r":2,"cap":0,"mega":1,"pop":4088000,"lon":96.165,"lat":16.785},{"n":"San Francisco","r":1,"cap":0,"mega":1,"pop":3450000,"lon":-122.4,"lat":37.784},{"n":"Denver","r":1,"cap":0,"mega":1,"pop":2313000,"lon":-104.986,"lat":39.741},{"n":"Houston","r":1,"cap":0,"mega":1,"pop":4459000,"lon":-95.348,"lat":29.741},{"n":"Miami","r":1,"cap":0,"mega":1,"pop":5585000,"lon":-80.226,"lat":25.79},{"n":"Atlanta","r":1,"cap":0,"mega":1,"pop":4506000,"lon":-84.368,"lat":33.739},{"n":"Chicago","r":1,"cap":0,"mega":1,"pop":8990000,"lon":-87.635,"lat":41.848},{"n":"Caracas","r":1,"cap":1,"mega":1,"pop":2985000,"lon":-66.919,"lat":10.503},{"n":"Kiev","r":1,"cap":1,"mega":1,"pop":2709000,"lon":30.515,"lat":50.435},{"n":"Dubai","r":1,"cap":0,"mega":1,"pop":1379000,"lon":55.287,"lat":25.215},{"n":"Tashkent","r":1,"cap":1,"mega":1,"pop":2184000,"lon":69.269,"lat":41.304},{"n":"Madrid","r":1,"cap":1,"mega":1,"pop":5567000,"lon":-3.685,"lat":40.402},{"n":"Geneva","r":1,"cap":0,"mega":0,"pop":1240000,"lon":6.14,"lat":46.21},{"n":"Stockholm","r":1,"cap":1,"mega":1,"pop":1264000,"lon":18.066,"lat":59.324},{"n":"Bangkok","r":1,"cap":1,"mega":1,"pop":6704000,"lon":100.515,"lat":13.752},{"n":"Lima","r":1,"cap":1,"mega":1,"pop":8012000,"lon":-77.052,"lat":-12.046},{"n":"Dakar","r":1,"cap":1,"mega":1,"pop":2604000,"lon":-17.475,"lat":14.718},{"n":"Johannesburg","r":1,"cap":0,"mega":1,"pop":3435000,"lon":28.028,"lat":-26.168},{"n":"Amsterdam","r":1,"cap":1,"mega":1,"pop":1031000,"lon":4.915,"lat":52.352},{"n":"Casablanca","r":1,"cap":0,"mega":1,"pop":3181000,"lon":-7.618,"lat":33.602},{"n":"Seoul","r":1,"cap":1,"mega":1,"pop":9796000,"lon":126.998,"lat":37.568},{"n":"Manila","r":1,"cap":1,"mega":1,"pop":11100000,"lon":120.98,"lat":14.606},{"n":"Monterrey","r":1,"cap":0,"mega":1,"pop":3712000,"lon":-100.332,"lat":25.672},{"n":"Berlin","r":1,"cap":1,"mega":1,"pop":3406000,"lon":13.4,"lat":52.524},{"n":"Urumqi","r":1,"cap":0,"mega":1,"pop":3575000,"lon":87.573,"lat":43.807},{"n":"Chengdu","r":1,"cap":0,"mega":1,"pop":4123000,"lon":104.068,"lat":30.672},{"n":"Osaka","r":1,"cap":0,"mega":1,"pop":11294000,"lon":135.504,"lat":34.691},{"n":"Kinshasa","r":1,"cap":1,"mega":1,"pop":7843000,"lon":15.313,"lat":-4.328},{"n":"New Delhi","r":1,"cap":1,"mega":0,"pop":317797,"lon":77.2,"lat":28.6},{"n":"Bengaluru","r":1,"cap":0,"mega":1,"pop":6787000,"lon":77.558,"lat":12.972},{"n":"Athens","r":1,"cap":1,"mega":1,"pop":3242000,"lon":23.731,"lat":37.985},{"n":"Baghdad","r":1,"cap":1,"mega":1,"pop":5054000,"lon":44.392,"lat":33.341},{"n":"Addis Ababa","r":1,"cap":1,"mega":1,"pop":3100000,"lon":38.698,"lat":9.035},{"n":"Tehran","r":1,"cap":1,"mega":1,"pop":7873000,"lon":51.422,"lat":35.674},{"n":"Vancouver","r":1,"cap":0,"mega":1,"pop":2313328,"lon":-123.124,"lat":49.275},{"n":"Toronto","r":1,"cap":0,"mega":1,"pop":5213000,"lon":-79.389,"lat":43.665},{"n":"Buenos Aires","r":1,"cap":1,"mega":1,"pop":12795000,"lon":-58.433,"lat":-34.611},{"n":"Kabul","r":1,"cap":1,"mega":1,"pop":3277000,"lon":69.181,"lat":34.519},{"n":"Vienna","r":1,"cap":1,"mega":1,"pop":2400000,"lon":16.365,"lat":48.202},{"n":"Melbourne","r":1,"cap":0,"mega":1,"pop":4170000,"lon":144.973,"lat":-37.818},{"n":"Taipei","r":1,"cap":1,"mega":1,"pop":6900273,"lon":121.568,"lat":25.036},{"n":"Auckland","r":1,"cap":0,"mega":1,"pop":1377200,"lon":174.763,"lat":-36.848},{"n":"Los Angeles","r":0,"cap":0,"mega":1,"pop":12500000,"lon":-118.232,"lat":34.049},{"n":"Washington, D.C.","r":0,"cap":1,"mega":1,"pop":4338000,"lon":-77.011,"lat":38.901},{"n":"New York","r":0,"cap":0,"mega":1,"pop":19040000,"lon":-73.996,"lat":40.722},{"n":"London","r":0,"cap":1,"mega":1,"pop":8567000,"lon":-0.119,"lat":51.502},{"n":"Istanbul","r":0,"cap":0,"mega":1,"pop":10061000,"lon":28.974,"lat":41.018},{"n":"Riyadh","r":0,"cap":1,"mega":1,"pop":4465000,"lon":46.72,"lat":24.634},{"n":"Cape Town","r":0,"cap":1,"mega":1,"pop":3215000,"lon":18.433,"lat":-33.918},{"n":"Moscow","r":0,"cap":1,"mega":1,"pop":10452000,"lon":37.614,"lat":55.754},{"n":"Mexico City","r":0,"cap":1,"mega":1,"pop":19028000,"lon":-99.133,"lat":19.444},{"n":"Lagos","r":0,"cap":0,"mega":1,"pop":9466000,"lon":3.39,"lat":6.445},{"n":"Rome","r":0,"cap":1,"mega":1,"pop":3339000,"lon":12.481,"lat":41.898},{"n":"Beijing","r":0,"cap":1,"mega":1,"pop":11106000,"lon":116.394,"lat":39.902},{"n":"Nairobi","r":0,"cap":1,"mega":1,"pop":3010000,"lon":36.815,"lat":-1.281},{"n":"Jakarta","r":0,"cap":1,"mega":1,"pop":9125000,"lon":106.827,"lat":-6.172},{"n":"Bogota","r":0,"cap":1,"mega":1,"pop":7772000,"lon":-74.085,"lat":4.598},{"n":"Cairo","r":0,"cap":1,"mega":1,"pop":11893000,"lon":31.248,"lat":30.052},{"n":"Shanghai","r":0,"cap":0,"mega":1,"pop":14987000,"lon":121.435,"lat":31.218},{"n":"Tokyo","r":0,"cap":1,"mega":1,"pop":35676000,"lon":139.749,"lat":35.687},{"n":"Mumbai","r":0,"cap":0,"mega":1,"pop":18978000,"lon":72.876,"lat":19.068},{"n":"Paris","r":0,"cap":1,"mega":1,"pop":9904000,"lon":2.353,"lat":48.858},{"n":"Santiago","r":0,"cap":1,"mega":1,"pop":5720000,"lon":-70.651,"lat":-33.44},{"n":"Kolkata","r":0,"cap":0,"mega":1,"pop":14787000,"lon":88.369,"lat":22.57},{"n":"Rio de Janeiro","r":0,"cap":0,"mega":1,"pop":11748000,"lon":-43.212,"lat":-22.907},{"n":"Sao Paulo","r":0,"cap":0,"mega":1,"pop":18845000,"lon":-46.627,"lat":-23.557},{"n":"Sydney","r":0,"cap":0,"mega":1,"pop":4630000,"lon":151.213,"lat":-33.871},{"n":"Singapore","r":0,"cap":1,"mega":1,"pop":5183700,"lon":103.854,"lat":1.295},{"n":"Hong Kong","r":0,"cap":0,"mega":1,"pop":7206000,"lon":114.183,"lat":22.307}]} \ No newline at end of file diff --git a/app/src/main/assets/world_land.geojson b/app/src/main/assets/world_land.geojson deleted file mode 100644 index f842c94d..00000000 --- a/app/src/main/assets/world_land.geojson +++ /dev/null @@ -1 +0,0 @@ -{"type":"GeometryCollection","geometries":[{"type":"Polygon","coordinates":[[[-59.57,-80.04],[-59.87,-80.55],[-60.16,-81.0],[-62.26,-80.86],[-64.49,-80.92],[-65.74,-80.59],[-65.74,-80.55],[-66.29,-80.26],[-64.04,-80.29],[-61.88,-80.39],[-61.14,-79.98],[-60.61,-79.63],[-59.57,-80.04]]]},{"type":"Polygon","coordinates":[[[-159.21,-79.5],[-161.13,-79.63],[-162.44,-79.28],[-163.03,-78.93],[-163.07,-78.87],[-163.71,-78.6],[-163.71,-78.6],[-163.11,-78.22],[-161.25,-78.38],[-160.25,-78.69],[-159.48,-79.05],[-159.21,-79.5]]]},{"type":"Polygon","coordinates":[[[-45.15,-78.05],[-43.92,-78.48],[-43.49,-79.09],[-43.37,-79.52],[-43.33,-80.03],[-44.88,-80.34],[-46.51,-80.59],[-48.39,-80.83],[-50.48,-81.03],[-52.85,-80.97],[-54.16,-80.63],[-53.99,-80.22],[-51.85,-79.95],[-50.99,-79.61],[-50.36,-79.18],[-49.91,-78.81],[-49.31,-78.46],[-48.66,-78.05],[-48.66,-78.05],[-48.15,-78.05],[-46.66,-77.83],[-45.15,-78.05]]]},{"type":"Polygon","coordinates":[[[-121.21,-73.5],[-119.92,-73.66],[-118.72,-73.48],[-119.29,-73.83],[-120.23,-74.09],[-121.62,-74.01],[-122.62,-73.66],[-122.62,-73.66],[-122.41,-73.32],[-121.21,-73.5]]]},{"type":"Polygon","coordinates":[[[-125.56,-73.48],[-124.03,-73.87],[-124.62,-73.83],[-125.91,-73.74],[-127.28,-73.46],[-127.28,-73.46],[-126.56,-73.25],[-125.56,-73.48]]]},{"type":"Polygon","coordinates":[[[-98.98,-71.93],[-97.88,-72.07],[-96.79,-71.95],[-96.2,-72.52],[-96.98,-72.44],[-98.2,-72.48],[-99.43,-72.44],[-100.78,-72.5],[-101.8,-72.31],[-102.33,-71.89],[-102.33,-71.89],[-101.7,-71.72],[-100.43,-71.85],[-98.98,-71.93]]]},{"type":"Polygon","coordinates":[[[-68.45,-70.96],[-68.33,-71.41],[-68.51,-71.8],[-68.78,-72.17],[-69.96,-72.31],[-71.08,-72.5],[-72.39,-72.48],[-71.9,-72.09],[-73.07,-72.23],[-74.19,-72.37],[-74.95,-72.07],[-75.01,-71.66],[-73.92,-71.27],[-73.92,-71.27],[-73.23,-71.15],[-72.07,-71.19],[-71.78,-70.68],[-71.72,-70.31],[-71.74,-69.51],[-71.17,-69.04],[-70.25,-68.88],[-69.72,-69.25],[-69.49,-69.62],[-69.06,-70.07],[-68.73,-70.51],[-68.45,-70.96]]]},{"type":"Polygon","coordinates":[[[-58.61,-64.15],[-59.05,-64.37],[-59.79,-64.21],[-60.61,-64.31],[-61.3,-64.54],[-62.02,-64.8],[-62.51,-65.09],[-62.65,-65.48],[-62.59,-65.86],[-62.12,-66.19],[-62.81,-66.43],[-63.75,-66.5],[-64.29,-66.84],[-64.88,-67.15],[-65.51,-67.58],[-65.67,-67.95],[-65.31,-68.37],[-64.78,-68.68],[-63.96,-68.91],[-63.2,-69.23],[-62.79,-69.62],[-62.57,-69.99],[-62.28,-70.38],[-61.81,-70.72],[-61.51,-71.09],[-61.38,-72.01],[-61.08,-72.38],[-61.0,-72.77],[-60.69,-73.17],[-60.83,-73.7],[-61.38,-74.11],[-61.96,-74.44],[-63.3,-74.58],[-63.75,-74.93],[-64.35,-75.26],[-65.86,-75.64],[-67.19,-75.79],[-68.45,-76.01],[-69.8,-76.22],[-70.6,-76.63],[-72.21,-76.67],[-73.97,-76.63],[-75.56,-76.71],[-77.24,-76.71],[-76.93,-77.1],[-75.4,-77.28],[-74.28,-77.56],[-73.66,-77.91],[-74.77,-78.22],[-76.5,-78.12],[-77.93,-78.38],[-77.98,-78.79],[-78.02,-79.18],[-76.85,-79.51],[-76.63,-79.89],[-75.36,-80.26],[-73.24,-80.42],[-71.44,-80.69],[-70.01,-81.0],[-68.19,-81.32],[-65.7,-81.47],[-63.26,-81.75],[-61.55,-82.04],[-59.69,-82.38],[-58.71,-82.85],[-58.22,-83.22],[-57.01,-82.87],[-55.36,-82.57],[-53.62,-82.26],[-51.54,-82.0],[-49.76,-81.73],[-47.27,-81.71],[-44.83,-81.85],[-42.81,-82.08],[-42.16,-81.65],[-40.77,-81.36],[-38.24,-81.34],[-36.27,-81.12],[-34.39,-80.91],[-32.31,-80.77],[-30.1,-80.59],[-28.55,-80.34],[-29.25,-79.99],[-29.69,-79.63],[-29.69,-79.26],[-31.62,-79.3],[-33.68,-79.46],[-35.64,-79.46],[-35.91,-79.08],[-35.78,-78.34],[-35.33,-78.12],[-33.9,-77.89],[-32.21,-77.65],[-31.0,-77.36],[-29.78,-77.07],[-28.88,-76.67],[-27.51,-76.5],[-26.16,-76.36],[-25.47,-76.28],[-23.93,-76.24],[-22.46,-76.11],[-21.22,-75.91],[-20.01,-75.67],[-18.91,-75.44],[-17.52,-75.13],[-16.64,-74.79],[-15.7,-74.5],[-15.41,-74.11],[-16.47,-73.87],[-16.11,-73.46],[-15.45,-73.15],[-14.41,-72.95],[-13.31,-72.72],[-12.29,-72.4],[-11.51,-72.01],[-11.02,-71.54],[-10.3,-71.27],[-9.1,-71.32],[-8.61,-71.66],[-7.42,-71.7],[-7.38,-71.32],[-6.87,-70.93],[-5.79,-71.03],[-5.54,-71.4],[-4.34,-71.46],[-3.05,-71.29],[-1.8,-71.17],[-0.66,-71.23],[-0.23,-71.64],[0.87,-71.3],[1.89,-71.13],[3.02,-70.99],[4.14,-70.85],[5.16,-70.62],[6.27,-70.46],[7.14,-70.25],[7.74,-69.89],[8.49,-70.15],[9.53,-70.01],[10.25,-70.48],[10.82,-70.83],[11.95,-70.64],[12.4,-70.25],[13.42,-69.97],[14.73,-70.03],[15.13,-70.4],[15.95,-70.03],[17.03,-69.91],[18.2,-69.87],[19.26,-69.89],[20.38,-70.01],[21.45,-70.07],[21.92,-70.4],[22.57,-70.7],[23.67,-70.52],[24.84,-70.48],[25.98,-70.48],[27.09,-70.46],[28.09,-70.32],[29.15,-70.21],[30.03,-69.93],[30.97,-69.76],[31.99,-69.66],[32.75,-69.38],[33.3,-68.84],[33.87,-68.5],[34.91,-68.66],[35.3,-69.01],[36.16,-69.25],[37.2,-69.17],[37.91,-69.52],[38.65,-69.78],[39.67,-69.54],[40.02,-69.11],[40.92,-68.93],[41.96,-68.6],[42.94,-68.46],[44.11,-68.27],[44.9,-68.05],[45.72,-67.82],[46.5,-67.6],[47.44,-67.72],[48.34,-67.37],[48.99,-67.09],[49.93,-67.11],[50.75,-66.88],[50.95,-66.52],[51.79,-66.25],[52.61,-66.05],[53.61,-65.9],[54.53,-65.82],[55.41,-65.88],[56.36,-65.97],[57.16,-66.25],[57.26,-66.68],[58.14,-67.01],[58.74,-67.29],[59.94,-67.41],[60.61,-67.68],[61.43,-67.95],[62.39,-68.01],[63.19,-67.82],[64.05,-67.41],[64.99,-67.62],[65.97,-67.74],[66.91,-67.86],[67.89,-67.93],[68.89,-67.93],[69.71,-68.97],[69.67,-69.23],[69.56,-69.68],[68.6,-69.93],[67.81,-70.31],[67.95,-70.7],[69.07,-70.68],[68.93,-71.07],[68.42,-71.44],[67.95,-71.85],[68.71,-72.17],[69.87,-72.26],[71.02,-72.09],[71.57,-71.7],[71.91,-71.32],[72.45,-71.01],[73.08,-70.72],[73.34,-70.36],[73.86,-69.87],[74.49,-69.78],[75.63,-69.74],[76.63,-69.62],[77.64,-69.46],[78.13,-69.07],[78.43,-68.7],[79.11,-68.33],[80.09,-68.07],[80.94,-67.88],[81.48,-67.54],[82.05,-67.37],[82.78,-67.21],[83.78,-67.31],[84.68,-67.21],[85.66,-67.09],[86.75,-67.15],[87.48,-66.88],[87.99,-66.21],[88.36,-66.48],[88.83,-66.95],[89.67,-67.15],[90.63,-67.23],[91.59,-67.11],[92.61,-67.19],[93.55,-67.21],[94.18,-67.11],[95.02,-67.17],[95.78,-67.39],[96.68,-67.25],[97.76,-67.25],[98.68,-67.11],[99.72,-67.25],[100.38,-66.92],[100.89,-66.58],[101.58,-66.31],[102.83,-65.56],[103.48,-65.7],[104.24,-65.97],[104.91,-66.33],[106.18,-66.93],[107.16,-66.95],[108.08,-66.95],[109.16,-66.84],[110.24,-66.7],[111.06,-66.43],[111.74,-66.13],[112.86,-66.09],[113.6,-65.88],[114.39,-66.07],[114.9,-66.39],[115.6,-66.7],[116.7,-66.66],[117.38,-66.92],[118.58,-67.17],[119.83,-67.27],[120.87,-67.19],[121.65,-66.88],[122.32,-66.56],[123.22,-66.48],[124.12,-66.62],[125.16,-66.72],[126.1,-66.56],[127.0,-66.56],[127.88,-66.66],[128.8,-66.76],[129.7,-66.58],[130.78,-66.43],[131.8,-66.39],[132.94,-66.39],[133.86,-66.29],[134.76,-66.21],[135.03,-65.72],[135.07,-65.31],[135.7,-65.58],[135.87,-66.03],[136.21,-66.45],[136.62,-66.78],[137.46,-66.95],[138.6,-66.9],[139.91,-66.88],[140.81,-66.82],[142.12,-66.82],[143.06,-66.8],[144.37,-66.84],[145.49,-66.92],[146.2,-67.23],[146.0,-67.6],[146.65,-67.9],[147.72,-68.13],[148.84,-68.39],[150.13,-68.56],[151.48,-68.72],[152.5,-68.87],[153.64,-68.89],[154.28,-68.56],[155.17,-68.84],[155.93,-69.15],[156.81,-69.38],[158.03,-69.48],[159.18,-69.6],[159.67,-69.99],[160.81,-70.23],[161.57,-70.58],[162.69,-70.74],[163.84,-70.72],[164.92,-70.78],[166.11,-70.76],[167.31,-70.83],[168.43,-70.97],[169.46,-71.21],[170.5,-71.4],[171.21,-71.7],[171.09,-72.09],[170.56,-72.44],[170.11,-72.89],[169.76,-73.24],[169.29,-73.66],[167.98,-73.81],[167.39,-74.17],[166.09,-74.38],[165.64,-74.77],[164.96,-75.15],[164.23,-75.46],[163.82,-75.87],[163.57,-76.24],[163.47,-76.69],[163.49,-77.07],[164.06,-77.46],[164.27,-77.83],[164.74,-78.18],[166.6,-78.32],[167.0,-78.75],[165.19,-78.91],[163.67,-79.12],[161.77,-79.16],[160.92,-79.73],[160.75,-80.2],[160.32,-80.57],[159.79,-80.95],[161.12,-81.28],[161.63,-81.69],[162.49,-82.06],[163.71,-82.4],[165.1,-82.71],[166.6,-83.02],[168.9,-83.34],[169.4,-83.83],[172.28,-84.04],[172.48,-84.12],[173.22,-84.41],[175.99,-84.16],[178.28,-84.47],[180,-84.71],[180,-90],[-180,-90],[-180,-84.71],[-179.94,-84.72],[-179.06,-84.14],[-177.26,-84.45],[-177.14,-84.42],[-176.86,-84.33],[-176.52,-84.23],[-176.23,-84.14],[-176.08,-84.1],[-175.93,-84.1],[-175.83,-84.12],[-174.38,-84.53],[-173.12,-84.12],[-172.89,-84.06],[-169.95,-83.88],[-169.0,-84.12],[-168.53,-84.24],[-167.02,-84.57],[-164.18,-84.83],[-161.93,-85.14],[-158.07,-85.37],[-155.19,-85.1],[-150.94,-85.3],[-148.53,-85.61],[-145.89,-85.32],[-143.11,-85.04],[-142.89,-84.57],[-146.83,-84.53],[-150.06,-84.3],[-150.9,-83.9],[-153.59,-83.69],[-153.41,-83.24],[-153.04,-82.83],[-152.67,-82.45],[-152.86,-82.04],[-154.53,-81.77],[-155.29,-81.42],[-156.84,-81.1],[-154.41,-81.16],[-152.1,-81.0],[-150.65,-81.34],[-148.87,-81.04],[-147.22,-80.67],[-146.42,-80.34],[-146.77,-79.93],[-148.06,-79.65],[-149.53,-79.36],[-151.59,-79.3],[-153.39,-79.16],[-155.33,-79.06],[-155.98,-78.69],[-157.27,-78.38],[-158.05,-78.03],[-158.37,-76.89],[-157.88,-76.99],[-156.97,-77.3],[-155.33,-77.2],[-153.74,-77.07],[-152.92,-77.5],[-151.33,-77.4],[-150.0,-77.18],[-148.75,-76.91],[-147.61,-76.58],[-146.1,-76.48],[-146.14,-76.11],[-146.5,-75.73],[-146.2,-75.38],[-144.91,-75.2],[-144.32,-75.54],[-142.79,-75.34],[-141.64,-75.09],[-140.21,-75.07],[-138.86,-74.97],[-137.51,-74.73],[-136.43,-74.52],[-135.21,-74.3],[-134.43,-74.36],[-133.75,-74.44],[-132.26,-74.3],[-130.93,-74.48],[-129.55,-74.46],[-128.24,-74.32],[-126.89,-74.42],[-125.4,-74.52],[-124.01,-74.48],[-122.56,-74.5],[-121.07,-74.52],[-119.7,-74.48],[-118.68,-74.19],[-117.47,-74.03],[-116.22,-74.24],[-115.02,-74.07],[-113.94,-73.71],[-113.3,-74.03],[-112.95,-74.38],[-112.3,-74.71],[-111.26,-74.42],[-110.07,-74.79],[-108.71,-74.91],[-107.56,-75.18],[-106.15,-75.13],[-104.88,-74.95],[-103.37,-74.99],[-102.02,-75.13],[-100.65,-75.3],[-100.12,-74.87],[-100.76,-74.54],[-101.25,-74.19],[-102.55,-74.11],[-103.11,-73.73],[-103.33,-73.36],[-103.68,-72.62],[-102.92,-72.75],[-101.61,-72.81],[-100.31,-72.75],[-99.14,-72.91],[-98.12,-73.21],[-97.69,-73.56],[-96.34,-73.62],[-95.04,-73.48],[-93.67,-73.28],[-92.44,-73.17],[-91.42,-73.4],[-90.09,-73.32],[-89.23,-72.56],[-88.42,-73.01],[-87.27,-73.19],[-86.01,-73.09],[-85.19,-73.48],[-83.88,-73.52],[-82.67,-73.64],[-81.47,-73.85],[-80.69,-73.48],[-80.3,-73.13],[-79.3,-73.52],[-77.93,-73.42],[-76.91,-73.64],[-76.22,-73.97],[-74.89,-73.87],[-73.85,-73.66],[-72.83,-73.4],[-71.62,-73.26],[-70.21,-73.15],[-68.94,-73.01],[-67.96,-72.79],[-67.37,-72.48],[-67.13,-72.05],[-67.25,-71.64],[-67.56,-71.25],[-67.92,-70.85],[-68.23,-70.46],[-68.49,-70.11],[-68.54,-69.72],[-68.45,-69.33],[-67.98,-68.95],[-67.58,-68.54],[-67.43,-68.15],[-67.62,-67.72],[-67.74,-67.33],[-67.25,-66.88],[-66.7,-66.58],[-66.06,-66.21],[-65.37,-65.9],[-64.57,-65.6],[-64.18,-65.17],[-63.63,-64.9],[-63.0,-64.64],[-62.04,-64.58],[-61.41,-64.27],[-60.71,-64.07],[-59.89,-63.96],[-59.16,-63.7],[-58.59,-63.39],[-57.81,-63.27],[-57.22,-63.53],[-57.6,-63.86],[-58.61,-64.15]]]},{"type":"Polygon","coordinates":[[[-67.75,-53.85],[-66.45,-54.45],[-65.05,-54.7],[-65.5,-55.2],[-66.45,-55.25],[-66.96,-54.9],[-67.29,-55.3],[-68.15,-55.61],[-69.23,-55.5],[-69.96,-55.2],[-71.01,-55.05],[-72.26,-54.5],[-73.29,-53.96],[-74.66,-52.84],[-73.84,-53.05],[-72.43,-53.72],[-71.11,-54.07],[-70.59,-53.62],[-70.27,-52.93],[-69.35,-52.52],[-68.63,-52.64],[-68.63,-52.64],[-68.25,-53.1],[-67.75,-53.85]]]},{"type":"Polygon","coordinates":[[[-58.55,-51.1],[-57.75,-51.55],[-58.05,-51.9],[-59.4,-52.2],[-59.85,-51.85],[-60.7,-52.3],[-61.2,-51.85],[-60,-51.25],[-59.15,-51.5],[-58.55,-51.1]]]},{"type":"Polygon","coordinates":[[[70.28,-49.71],[68.75,-49.77],[68.72,-49.24],[68.87,-48.83],[68.94,-48.62],[69.58,-48.94],[70.53,-49.06],[70.56,-49.26],[70.28,-49.71]]]},{"type":"Polygon","coordinates":[[[145.4,-40.79],[146.36,-41.14],[146.91,-41.0],[147.69,-40.81],[148.29,-40.88],[148.36,-42.06],[148.02,-42.41],[147.91,-43.21],[147.56,-42.94],[146.87,-43.63],[146.66,-43.58],[146.05,-43.55],[145.43,-42.69],[145.3,-42.03],[144.72,-41.16],[144.74,-40.7],[145.4,-40.79]]]},{"type":"Polygon","coordinates":[[[173.02,-40.92],[173.25,-41.33],[173.96,-40.93],[174.25,-41.35],[174.25,-41.77],[173.88,-42.23],[173.22,-42.97],[172.71,-43.37],[173.08,-43.85],[172.31,-43.87],[171.45,-44.24],[171.19,-44.9],[170.62,-45.91],[169.83,-46.36],[169.33,-46.64],[168.41,-46.62],[167.76,-46.29],[166.68,-46.22],[166.51,-45.85],[167.05,-45.11],[168.3,-44.12],[168.95,-43.94],[169.67,-43.56],[170.52,-43.03],[171.13,-42.51],[171.57,-41.77],[171.95,-41.51],[172.1,-40.96],[172.8,-40.49],[173.02,-40.92]]]},{"type":"Polygon","coordinates":[[[174.61,-36.16],[175.34,-37.21],[175.36,-36.53],[175.81,-36.8],[175.96,-37.56],[176.76,-37.88],[177.44,-37.96],[178.01,-37.58],[178.52,-37.7],[178.27,-38.58],[177.97,-39.17],[177.21,-39.15],[176.94,-39.45],[177.03,-39.88],[176.89,-40.07],[176.51,-40.6],[176.01,-41.29],[175.24,-41.69],[175.07,-41.43],[174.65,-41.28],[175.23,-40.46],[174.9,-39.91],[173.82,-39.51],[173.85,-39.15],[174.57,-38.8],[174.74,-38.03],[174.7,-37.38],[174.29,-36.71],[174.32,-36.53],[173.84,-36.12],[173.05,-35.24],[172.64,-34.53],[173.01,-34.45],[173.55,-35.01],[174.33,-35.27],[174.61,-36.16]]]},{"type":"Polygon","coordinates":[[[167.12,-22.16],[166.74,-22.4],[166.19,-22.13],[165.47,-21.68],[164.83,-21.15],[164.17,-20.44],[164.03,-20.11],[164.46,-20.12],[165.02,-20.46],[165.46,-20.8],[165.78,-21.08],[166.6,-21.7],[167.12,-22.16]]]},{"type":"Polygon","coordinates":[[[178.37,-17.34],[178.72,-17.63],[178.55,-18.15],[177.93,-18.29],[177.38,-18.16],[177.29,-17.72],[177.67,-17.38],[178.13,-17.5],[178.37,-17.34]]]},{"type":"Polygon","coordinates":[[[179.36,-16.8],[178.73,-17.01],[178.6,-16.64],[179.1,-16.43],[179.41,-16.38],[180,-16.07],[180,-16.56],[179.36,-16.8]]]},{"type":"Polygon","coordinates":[[[-179.92,-16.5],[-180,-16.56],[-180,-16.07],[-179.79,-16.02],[-179.92,-16.5]]]},{"type":"Polygon","coordinates":[[[167.84,-16.47],[167.52,-16.6],[167.18,-16.16],[167.22,-15.89],[167.84,-16.47]]]},{"type":"Polygon","coordinates":[[[167.11,-14.93],[167.27,-15.74],[167.0,-15.61],[166.79,-15.67],[166.65,-15.39],[166.63,-14.63],[167.11,-14.93]]]},{"type":"Polygon","coordinates":[[[50.06,-13.56],[50.22,-14.76],[50.48,-15.23],[50.38,-15.71],[50.2,-16.0],[49.86,-15.41],[49.67,-15.71],[49.86,-16.45],[49.77,-16.88],[49.5,-17.11],[49.44,-17.95],[49.04,-19.12],[48.55,-20.5],[47.93,-22.39],[47.55,-23.78],[47.1,-24.94],[46.28,-25.18],[45.41,-25.6],[44.83,-25.35],[44.04,-24.99],[43.76,-24.46],[43.7,-23.57],[43.35,-22.78],[43.25,-22.06],[43.43,-21.34],[43.89,-21.16],[43.9,-20.83],[44.37,-20.07],[44.46,-19.44],[44.23,-18.96],[44.04,-18.33],[43.96,-17.41],[44.31,-16.85],[44.45,-16.22],[44.94,-16.18],[45.5,-15.97],[45.87,-15.79],[46.31,-15.78],[46.88,-15.21],[47.71,-14.59],[48.01,-14.09],[47.87,-13.66],[48.29,-13.78],[48.85,-13.09],[48.86,-12.49],[49.19,-12.04],[49.54,-12.47],[49.81,-12.9],[50.06,-13.56]]]},{"type":"Polygon","coordinates":[[[143.56,-13.76],[143.92,-14.55],[144.56,-14.17],[144.89,-14.59],[145.37,-14.98],[145.27,-15.43],[145.49,-16.29],[145.64,-16.78],[145.89,-16.91],[146.16,-17.76],[146.06,-18.28],[146.39,-18.96],[147.47,-19.48],[148.18,-19.96],[148.85,-20.39],[148.72,-20.63],[149.29,-21.26],[149.68,-22.34],[150.08,-22.12],[150.48,-22.56],[150.73,-22.4],[150.9,-23.46],[151.61,-24.08],[152.07,-24.46],[152.86,-25.27],[153.14,-26.07],[153.16,-26.64],[153.09,-27.26],[153.57,-28.11],[153.51,-29.0],[153.34,-29.46],[153.07,-30.35],[153.09,-30.92],[152.89,-31.64],[152.45,-32.55],[151.71,-33.04],[151.34,-33.82],[151.01,-34.31],[150.71,-35.17],[150.33,-35.67],[150.08,-36.42],[149.95,-37.11],[150.0,-37.43],[149.42,-37.77],[148.3,-37.81],[147.38,-38.22],[146.92,-38.61],[146.32,-39.04],[145.49,-38.59],[144.88,-38.42],[145.03,-37.9],[144.49,-38.09],[143.61,-38.81],[142.75,-38.54],[142.18,-38.38],[141.61,-38.31],[140.64,-38.02],[139.99,-37.4],[139.81,-36.64],[139.57,-36.14],[139.08,-35.73],[138.12,-35.61],[138.45,-35.13],[138.21,-34.38],[137.72,-35.08],[136.83,-35.26],[137.35,-34.71],[137.5,-34.13],[137.89,-33.64],[137.81,-32.9],[137.0,-33.75],[136.37,-34.09],[135.99,-34.89],[135.21,-34.48],[135.24,-33.95],[134.61,-33.22],[134.09,-32.85],[134.27,-32.62],[132.99,-32.01],[132.29,-31.98],[131.33,-31.5],[129.54,-31.59],[128.24,-31.95],[127.1,-32.28],[126.15,-32.22],[125.09,-32.73],[124.22,-32.96],[124.03,-33.48],[123.66,-33.89],[122.81,-33.91],[122.18,-34.0],[121.3,-33.82],[120.58,-33.93],[119.89,-33.98],[119.3,-34.51],[119.01,-34.46],[118.51,-34.75],[118.02,-35.06],[117.3,-35.03],[116.63,-35.03],[115.56,-34.39],[115.03,-34.2],[115.05,-33.62],[115.55,-33.49],[115.71,-33.26],[115.68,-32.9],[115.8,-32.21],[115.69,-31.61],[115.16,-30.6],[115.0,-30.03],[115.04,-29.46],[114.64,-28.81],[114.62,-28.52],[114.17,-28.12],[114.05,-27.33],[113.48,-26.54],[113.34,-26.12],[113.78,-26.55],[113.44,-25.62],[113.94,-25.91],[114.23,-26.3],[114.22,-25.79],[113.72,-25.0],[113.63,-24.68],[113.39,-24.38],[113.5,-23.81],[113.71,-23.56],[113.84,-23.06],[113.74,-22.48],[114.15,-21.76],[114.23,-22.52],[114.65,-21.83],[115.46,-21.5],[115.95,-21.07],[116.71,-20.7],[117.17,-20.62],[117.44,-20.75],[118.23,-20.37],[118.84,-20.26],[118.99,-20.04],[119.25,-19.95],[119.81,-19.98],[120.86,-19.68],[121.4,-19.24],[121.66,-18.71],[122.24,-18.2],[122.29,-17.8],[122.31,-17.25],[123.01,-16.41],[123.43,-17.27],[123.86,-17.07],[123.5,-16.6],[123.82,-16.11],[124.26,-16.33],[124.38,-15.57],[124.93,-15.08],[125.17,-14.68],[125.67,-14.51],[125.69,-14.23],[126.13,-14.35],[126.14,-14.1],[126.58,-13.95],[127.07,-13.82],[127.8,-14.28],[128.36,-14.87],[128.99,-14.88],[129.62,-14.97],[129.41,-14.42],[129.89,-13.62],[130.34,-13.36],[130.18,-13.11],[130.62,-12.54],[131.22,-12.18],[131.74,-12.3],[132.58,-12.11],[132.56,-11.6],[131.82,-11.27],[132.36,-11.13],[133.02,-11.38],[133.55,-11.79],[134.39,-12.04],[134.68,-11.94],[135.3,-12.25],[135.88,-11.96],[136.26,-12.05],[136.49,-11.86],[136.95,-12.35],[136.69,-12.89],[136.31,-13.29],[135.96,-13.32],[136.08,-13.72],[135.78,-14.22],[135.43,-14.72],[135.5,-15.0],[136.3,-15.55],[137.07,-15.87],[137.58,-16.22],[138.3,-16.81],[138.59,-16.81],[139.11,-17.06],[139.26,-17.37],[140.22,-17.71],[140.88,-17.37],[141.07,-16.83],[141.27,-16.39],[141.4,-15.84],[141.7,-15.04],[141.56,-14.56],[141.64,-14.27],[141.52,-13.7],[141.65,-12.94],[141.84,-12.74],[141.69,-12.41],[141.93,-11.88],[142.12,-11.33],[142.14,-11.04],[142.52,-10.67],[142.8,-11.16],[142.87,-11.78],[143.12,-11.91],[143.16,-12.33],[143.52,-12.83],[143.6,-13.4],[143.56,-13.76]]]},{"type":"Polygon","coordinates":[[[162.12,-10.48],[162.4,-10.83],[161.7,-10.82],[161.32,-10.2],[161.92,-10.45],[162.12,-10.48]]]},{"type":"Polygon","coordinates":[[[120.72,-10.24],[120.3,-10.26],[118.97,-9.56],[119.9,-9.36],[120.43,-9.67],[120.78,-9.97],[120.72,-10.24]]]},{"type":"Polygon","coordinates":[[[160.85,-9.87],[160.46,-9.9],[159.85,-9.79],[159.64,-9.64],[159.7,-9.24],[160.36,-9.4],[160.69,-9.61],[160.85,-9.87]]]},{"type":"Polygon","coordinates":[[[161.68,-9.6],[161.53,-9.78],[160.79,-8.92],[160.58,-8.32],[160.92,-8.32],[161.28,-9.12],[161.68,-9.6]]]},{"type":"Polygon","coordinates":[[[124.44,-10.14],[123.58,-10.36],[123.46,-10.24],[123.55,-9.9],[123.98,-9.29],[124.97,-8.89],[125.09,-8.66],[125.95,-8.43],[126.64,-8.4],[126.96,-8.27],[127.34,-8.4],[126.97,-8.67],[125.93,-9.11],[125.09,-9.39],[124.44,-10.14]]]},{"type":"Polygon","coordinates":[[[117.9,-8.1],[118.26,-8.36],[118.88,-8.28],[119.13,-8.71],[117.97,-8.91],[117.28,-9.04],[116.74,-9.03],[117.08,-8.46],[117.63,-8.45],[117.9,-8.1]]]},{"type":"Polygon","coordinates":[[[122.9,-8.09],[122.76,-8.65],[121.25,-8.93],[119.92,-8.81],[119.92,-8.44],[120.72,-8.24],[121.34,-8.54],[122.01,-8.46],[122.9,-8.09]]]},{"type":"Polygon","coordinates":[[[159.88,-8.34],[159.92,-8.54],[159.13,-8.11],[158.59,-7.75],[158.21,-7.42],[158.36,-7.32],[158.82,-7.56],[159.64,-8.02],[159.88,-8.34]]]},{"type":"Polygon","coordinates":[[[157.54,-7.35],[157.34,-7.4],[156.9,-7.18],[156.49,-6.77],[156.54,-6.6],[157.14,-7.02],[157.54,-7.35]]]},{"type":"Polygon","coordinates":[[[108.62,-6.78],[110.54,-6.88],[110.76,-6.47],[112.61,-6.95],[112.98,-7.59],[114.48,-7.78],[115.71,-8.37],[114.56,-8.75],[113.46,-8.35],[112.56,-8.38],[111.52,-8.3],[110.59,-8.12],[109.43,-7.74],[108.69,-7.64],[108.28,-7.77],[106.45,-7.35],[106.28,-6.92],[105.37,-6.85],[106.05,-5.9],[107.27,-5.95],[108.07,-6.35],[108.49,-6.42],[108.62,-6.78]]]},{"type":"Polygon","coordinates":[[[134.72,-6.21],[134.21,-6.9],[134.11,-6.14],[134.29,-5.78],[134.5,-5.45],[134.73,-5.74],[134.72,-6.21]]]},{"type":"Polygon","coordinates":[[[155.88,-6.82],[155.6,-6.92],[155.17,-6.54],[154.73,-5.9],[154.51,-5.14],[154.65,-5.04],[154.76,-5.34],[155.06,-5.57],[155.55,-6.2],[156.02,-6.54],[155.88,-6.82]]]},{"type":"Polygon","coordinates":[[[151.98,-5.48],[151.46,-5.56],[151.3,-5.84],[150.75,-6.08],[150.24,-6.32],[149.71,-6.32],[148.89,-6.03],[148.32,-5.75],[148.4,-5.44],[149.3,-5.58],[149.85,-5.51],[150.0,-5.03],[150.14,-5.0],[150.24,-5.53],[150.81,-5.46],[151.09,-5.11],[151.65,-4.76],[151.54,-4.17],[152.14,-4.15],[152.34,-4.31],[152.32,-4.87],[151.98,-5.48]]]},{"type":"Polygon","coordinates":[[[127.25,-3.46],[126.87,-3.79],[126.18,-3.61],[125.99,-3.18],[127.0,-3.13],[127.25,-3.46]]]},{"type":"Polygon","coordinates":[[[130.47,-3.09],[130.83,-3.86],[129.99,-3.45],[129.16,-3.36],[128.59,-3.43],[127.9,-3.39],[128.14,-2.84],[129.37,-2.8],[130.47,-3.09]]]},{"type":"Polygon","coordinates":[[[153.14,-4.5],[152.83,-4.77],[152.64,-4.18],[152.41,-3.79],[151.95,-3.46],[151.38,-3.04],[150.66,-2.74],[150.94,-2.5],[151.48,-2.78],[151.82,-3.0],[152.24,-3.24],[152.64,-3.66],[153.02,-3.98],[153.14,-4.5]]]},{"type":"Polygon","coordinates":[[[134.14,-1.15],[134.42,-2.77],[135.46,-3.37],[136.29,-2.31],[137.44,-1.7],[138.33,-1.7],[139.18,-2.05],[139.93,-2.41],[141.0,-2.6],[142.74,-3.29],[144.58,-3.86],[145.27,-4.37],[145.83,-4.88],[145.98,-5.47],[147.65,-6.08],[147.89,-6.61],[146.97,-6.72],[147.19,-7.39],[148.08,-8.04],[148.73,-9.1],[149.31,-9.07],[149.27,-9.51],[150.04,-9.68],[149.74,-9.87],[150.8,-10.29],[150.69,-10.58],[150.03,-10.65],[149.78,-10.39],[148.92,-10.28],[147.91,-10.13],[147.14,-9.49],[146.57,-8.94],[146.05,-8.07],[144.74,-7.63],[143.9,-7.92],[143.29,-8.25],[143.41,-8.98],[142.63,-9.33],[142.07,-9.16],[141.03,-9.12],[140.14,-8.3],[139.13,-8.1],[138.88,-8.38],[137.61,-8.41],[138.04,-7.6],[138.67,-7.32],[138.41,-6.23],[137.93,-5.39],[135.99,-4.55],[135.16,-4.46],[133.66,-3.54],[133.37,-4.02],[132.98,-4.11],[132.76,-3.75],[132.75,-3.31],[131.99,-2.82],[133.07,-2.46],[133.78,-2.48],[133.7,-2.21],[132.23,-2.21],[131.84,-1.62],[130.94,-1.43],[130.52,-0.94],[131.87,-0.7],[132.38,-0.37],[133.99,-0.78],[134.14,-1.15]]]},{"type":"Polygon","coordinates":[[[125.24,1.42],[124.44,0.43],[123.69,0.24],[122.72,0.43],[121.06,0.38],[120.18,0.24],[120.04,-0.52],[120.94,-1.41],[121.48,-0.96],[123.34,-0.62],[123.26,-1.08],[122.82,-0.93],[122.39,-1.52],[121.51,-1.9],[122.45,-3.19],[122.27,-3.53],[123.17,-4.68],[123.16,-5.34],[122.63,-5.63],[122.24,-5.28],[122.72,-4.46],[121.74,-4.85],[121.49,-4.57],[121.62,-4.19],[120.9,-3.6],[120.97,-2.63],[120.31,-2.93],[120.39,-4.1],[120.43,-5.53],[119.8,-5.67],[119.37,-5.38],[119.65,-4.46],[119.5,-3.49],[119.08,-3.49],[118.77,-2.8],[119.18,-2.15],[119.32,-1.35],[119.83,0.15],[120.04,0.57],[120.89,1.31],[121.67,1.01],[122.93,0.88],[124.08,0.92],[125.07,1.64],[125.24,1.42]]]},{"type":"Polygon","coordinates":[[[128.69,1.13],[128.64,0.26],[128.12,0.36],[127.97,-0.25],[128.38,-0.78],[128.1,-0.9],[127.7,-0.27],[127.4,1.01],[127.6,1.81],[127.93,2.17],[128.0,1.63],[128.59,1.54],[128.69,1.13]]]},{"type":"Polygon","coordinates":[[[105.82,-5.85],[104.71,-5.87],[103.87,-5.04],[102.58,-4.22],[102.16,-3.61],[101.4,-2.8],[100.9,-2.05],[100.14,-0.65],[99.26,0.18],[98.97,1.04],[98.6,1.82],[97.7,2.45],[97.18,3.31],[96.42,3.87],[95.38,4.97],[95.29,5.48],[95.94,5.44],[97.48,5.25],[98.37,4.27],[99.14,3.59],[99.69,3.17],[100.64,2.1],[101.66,2.08],[102.5,1.4],[103.08,0.56],[103.84,0.1],[103.44,-0.71],[104.01,-1.06],[104.37,-1.08],[104.54,-1.78],[104.89,-2.34],[105.62,-2.43],[106.11,-3.06],[105.86,-4.31],[105.82,-5.85]]]},{"type":"Polygon","coordinates":[[[117.88,1.83],[119.0,0.9],[117.81,0.78],[117.48,0.1],[117.52,-0.8],[116.56,-1.49],[116.53,-2.48],[116.15,-4.01],[116.0,-3.66],[114.86,-4.11],[114.47,-3.5],[113.76,-3.44],[113.26,-3.12],[112.07,-3.48],[111.7,-2.99],[111.05,-3.05],[110.22,-2.93],[110.07,-1.59],[109.57,-1.31],[109.09,-0.46],[108.95,0.42],[109.07,1.34],[109.66,2.01],[110.4,1.66],[111.17,1.85],[111.37,2.7],[111.8,2.89],[113.0,3.1],[113.71,3.89],[114.2,4.53],[114.6,4.9],[115.45,5.45],[116.22,6.14],[116.73,6.92],[117.13,6.93],[117.64,6.42],[117.69,5.99],[118.35,5.71],[119.18,5.41],[119.11,5.02],[118.44,4.97],[118.62,4.48],[117.88,4.14],[117.31,3.23],[118.05,2.29],[117.88,1.83]]]},{"type":"Polygon","coordinates":[[[126.38,8.41],[126.48,7.75],[126.54,7.19],[126.2,6.27],[125.83,7.29],[125.36,6.79],[125.68,6.05],[125.4,5.58],[124.22,6.16],[123.94,6.89],[124.24,7.36],[123.61,7.83],[123.3,7.42],[122.83,7.46],[122.09,6.9],[121.92,7.19],[122.31,8.03],[122.94,8.32],[123.49,8.69],[123.84,8.24],[124.6,8.51],[124.76,8.96],[125.47,8.99],[125.41,9.76],[126.22,9.29],[126.31,8.78],[126.38,8.41]]]},{"type":"Polygon","coordinates":[[[81.22,6.2],[80.35,5.97],[79.87,6.76],[79.7,8.2],[80.15,9.82],[80.84,9.27],[81.3,8.56],[81.79,7.52],[81.64,6.48],[81.22,6.2]]]},{"type":"Polygon","coordinates":[[[-60.94,10.11],[-61.77,10],[-61.95,10.09],[-61.66,10.37],[-61.68,10.76],[-61.1,10.89],[-60.9,10.86],[-60.94,10.11]]]},{"type":"Polygon","coordinates":[[[123.98,10.28],[123.62,9.95],[123.31,9.32],[123.0,9.02],[122.38,9.71],[122.59,9.98],[122.84,10.26],[122.95,10.88],[123.5,10.94],[123.34,10.27],[124.08,11.23],[123.98,10.28]]]},{"type":"Polygon","coordinates":[[[118.5,9.32],[117.17,8.37],[117.66,9.07],[118.39,9.68],[118.99,10.38],[119.51,11.37],[119.69,10.55],[119.03,10.0],[118.5,9.32]]]},{"type":"Polygon","coordinates":[[[121.88,11.89],[122.48,11.58],[123.12,11.58],[123.1,11.17],[122.64,10.74],[122.0,10.44],[121.97,10.91],[122.04,11.42],[121.88,11.89]]]},{"type":"Polygon","coordinates":[[[125.5,12.16],[125.78,11.05],[125.01,11.31],[125.03,10.98],[125.28,10.36],[124.8,10.13],[124.76,10.84],[124.46,10.89],[124.3,11.5],[124.89,11.42],[124.88,11.79],[124.27,12.56],[125.23,12.54],[125.5,12.16]]]},{"type":"Polygon","coordinates":[[[121.53,13.07],[121.26,12.21],[120.83,12.7],[120.32,13.47],[121.18,13.43],[121.53,13.07]]]},{"type":"Polygon","coordinates":[[[121.32,18.5],[121.94,18.22],[122.25,18.48],[122.34,18.22],[122.17,17.81],[122.52,17.09],[122.25,16.26],[121.66,15.93],[121.51,15.12],[121.73,14.33],[122.26,14.22],[122.7,14.34],[123.95,13.78],[123.86,13.24],[124.18,13.0],[124.08,12.54],[123.3,13.03],[122.93,13.55],[122.67,13.19],[122.03,13.78],[121.13,13.64],[120.63,13.86],[120.68,14.27],[120.99,14.53],[120.69,14.76],[120.56,14.4],[120.07,14.97],[119.92,15.41],[119.88,16.36],[120.29,16.03],[120.39,17.6],[120.72,18.51],[121.32,18.5]]]},{"type":"Polygon","coordinates":[[[-65.59,18.23],[-65.85,17.98],[-66.6,17.98],[-67.18,17.95],[-67.24,18.37],[-67.1,18.52],[-66.28,18.51],[-65.77,18.43],[-65.59,18.23]]]},{"type":"Polygon","coordinates":[[[-76.9,17.87],[-77.21,17.7],[-77.77,17.86],[-78.34,18.23],[-78.22,18.45],[-77.8,18.52],[-77.57,18.49],[-76.9,18.4],[-76.37,18.16],[-76.2,17.89],[-76.9,17.87]]]},{"type":"Polygon","coordinates":[[[-72.58,19.87],[-71.71,19.71],[-71.59,19.88],[-70.81,19.88],[-70.21,19.62],[-69.95,19.65],[-69.77,19.29],[-69.22,19.31],[-69.25,19.02],[-68.81,18.98],[-68.32,18.61],[-68.69,18.21],[-69.16,18.42],[-69.62,18.38],[-69.95,18.43],[-70.13,18.25],[-70.52,18.18],[-70.67,18.43],[-71.0,18.28],[-71.4,17.6],[-71.66,17.76],[-71.71,18.04],[-72.37,18.21],[-72.84,18.15],[-73.45,18.22],[-73.92,18.03],[-74.46,18.34],[-74.37,18.66],[-73.45,18.53],[-72.69,18.45],[-72.33,18.67],[-72.79,19.1],[-72.78,19.48],[-73.42,19.64],[-73.19,19.92],[-72.58,19.87]]]},{"type":"Polygon","coordinates":[[[110.34,18.68],[109.48,18.2],[108.66,18.51],[108.63,19.37],[109.12,19.82],[110.21,20.1],[110.79,20.08],[111.01,19.7],[110.57,19.26],[110.34,18.68]]]},{"type":"Polygon","coordinates":[[[-155.54,19.08],[-155.69,18.92],[-155.94,19.06],[-155.91,19.34],[-156.07,19.7],[-156.02,19.81],[-155.85,19.98],[-155.92,20.17],[-155.86,20.27],[-155.79,20.25],[-155.4,20.08],[-155.22,19.99],[-155.06,19.86],[-154.81,19.51],[-154.83,19.45],[-155.22,19.24],[-155.54,19.08]]]},{"type":"Polygon","coordinates":[[[-156.08,20.64],[-156.41,20.57],[-156.59,20.78],[-156.7,20.86],[-156.71,20.93],[-156.61,21.01],[-156.26,20.92],[-156.0,20.76],[-156.08,20.64]]]},{"type":"Polygon","coordinates":[[[-156.76,21.18],[-156.79,21.07],[-157.33,21.1],[-157.25,21.22],[-156.76,21.18]]]},{"type":"Polygon","coordinates":[[[-157.65,21.32],[-157.71,21.26],[-157.78,21.28],[-158.13,21.31],[-158.25,21.54],[-158.29,21.58],[-158.03,21.72],[-157.94,21.65],[-157.65,21.32]]]},{"type":"Polygon","coordinates":[[[-159.35,21.98],[-159.46,21.88],[-159.8,22.07],[-159.75,22.14],[-159.6,22.24],[-159.37,22.21],[-159.35,21.98]]]},{"type":"Polygon","coordinates":[[[-79.68,22.77],[-79.28,22.4],[-78.35,22.51],[-77.99,22.28],[-77.15,21.66],[-76.52,21.21],[-76.19,21.22],[-75.6,21.02],[-75.67,20.74],[-74.93,20.69],[-74.18,20.28],[-74.3,20.05],[-74.96,19.92],[-75.63,19.87],[-76.32,19.95],[-77.76,19.86],[-77.09,20.41],[-77.49,20.67],[-78.14,20.74],[-78.48,21.03],[-78.72,21.6],[-79.28,21.56],[-80.22,21.83],[-80.52,22.04],[-81.82,22.19],[-82.17,22.39],[-81.8,22.64],[-82.78,22.69],[-83.49,22.17],[-83.91,22.15],[-84.05,21.91],[-84.55,21.8],[-84.97,21.9],[-84.45,22.2],[-84.23,22.57],[-83.78,22.79],[-83.27,22.98],[-82.51,23.08],[-82.27,23.19],[-81.4,23.12],[-80.62,23.11],[-79.68,22.77]]]},{"type":"Polygon","coordinates":[[[-77.53,23.76],[-77.78,23.71],[-78.03,24.29],[-78.41,24.58],[-78.19,25.21],[-77.89,25.17],[-77.54,24.34],[-77.53,23.76]]]},{"type":"Polygon","coordinates":[[[121.18,22.79],[120.75,21.97],[120.22,22.81],[120.11,23.56],[120.69,24.54],[121.5,25.3],[121.95,25.0],[121.78,24.39],[121.18,22.79]]]},{"type":"Polygon","coordinates":[[[-77.82,26.58],[-78.91,26.42],[-78.98,26.79],[-78.51,26.87],[-77.85,26.84],[-77.82,26.58]]]},{"type":"Polygon","coordinates":[[[-77,26.59],[-77.17,25.88],[-77.36,26.01],[-77.34,26.53],[-77.79,26.93],[-77.79,27.04],[-77,26.59]]]},{"type":"Polygon","coordinates":[[[134.64,34.15],[134.77,33.81],[134.2,33.2],[133.79,33.52],[133.28,33.29],[133.01,32.7],[132.36,32.99],[132.37,33.46],[132.92,34.06],[133.49,33.94],[133.9,34.36],[134.64,34.15]]]},{"type":"Polygon","coordinates":[[[34.58,35.67],[33.9,35.25],[33.97,35.06],[34.0,34.98],[32.98,34.57],[32.49,34.7],[32.26,35.1],[32.73,35.14],[32.8,35.15],[32.95,35.39],[33.67,35.37],[34.58,35.67]]]},{"type":"Polygon","coordinates":[[[23.7,35.71],[24.25,35.37],[25.03,35.42],[25.77,35.35],[25.75,35.18],[26.29,35.3],[26.16,35.0],[24.72,34.92],[24.74,35.08],[23.51,35.28],[23.7,35.71]]]},{"type":"Polygon","coordinates":[[[15.52,38.23],[15.16,37.44],[15.31,37.13],[15.1,36.62],[14.34,37.0],[13.83,37.1],[12.43,37.61],[12.57,38.13],[13.74,38.03],[14.76,38.14],[15.52,38.23]]]},{"type":"Polygon","coordinates":[[[9.21,41.21],[9.81,40.5],[9.67,39.18],[9.21,39.24],[8.81,38.91],[8.43,39.17],[8.39,40.38],[8.16,40.95],[8.71,40.9],[9.21,41.21]]]},{"type":"Polygon","coordinates":[[[140.98,37.14],[140.6,36.34],[140.77,35.84],[140.25,35.14],[138.98,34.67],[137.22,34.61],[135.79,33.46],[135.12,33.85],[135.08,34.6],[133.34,34.38],[132.16,33.9],[130.99,33.89],[132.0,33.15],[131.33,31.45],[130.69,31.03],[130.2,31.42],[130.45,32.32],[129.81,32.61],[129.41,33.3],[130.35,33.6],[130.88,34.23],[131.88,34.75],[132.62,35.43],[134.61,35.73],[135.68,35.53],[136.72,37.3],[137.39,36.83],[138.86,37.83],[139.43,38.22],[140.05,39.44],[139.88,40.56],[140.31,41.2],[141.37,41.38],[141.91,39.99],[141.88,39.18],[140.96,38.17],[140.98,37.14]]]},{"type":"Polygon","coordinates":[[[9.56,42.15],[9.23,41.38],[8.78,41.58],[8.54,42.26],[8.75,42.63],[9.39,43.01],[9.56,42.15]]]},{"type":"Polygon","coordinates":[[[143.91,44.17],[144.61,43.96],[145.32,44.38],[145.54,43.26],[144.06,42.99],[143.18,42.0],[141.61,42.68],[141.07,41.58],[139.96,41.57],[139.82,42.56],[140.31,43.33],[141.38,43.39],[141.67,44.77],[141.97,45.55],[143.14,44.51],[143.91,44.17]]]},{"type":"Polygon","coordinates":[[[-63.66,46.55],[-62.94,46.42],[-62.01,46.44],[-62.5,46.03],[-62.87,45.97],[-64.14,46.39],[-64.39,46.73],[-64.01,47.04],[-63.66,46.55]]]},{"type":"Polygon","coordinates":[[[-61.81,49.11],[-62.29,49.09],[-63.59,49.4],[-64.52,49.87],[-64.17,49.96],[-62.86,49.71],[-61.84,49.29],[-61.81,49.11]]]},{"type":"Polygon","coordinates":[[[-123.51,48.51],[-124.01,48.37],[-125.66,48.83],[-125.95,49.18],[-126.85,49.53],[-127.03,49.81],[-128.06,49.99],[-128.44,50.54],[-128.36,50.77],[-127.31,50.55],[-126.7,50.4],[-125.76,50.3],[-125.42,49.95],[-124.92,49.48],[-123.92,49.06],[-123.51,48.51]]]},{"type":"Polygon","coordinates":[[[-56.13,50.69],[-56.8,49.81],[-56.14,50.15],[-55.47,49.94],[-55.82,49.59],[-54.94,49.31],[-54.47,49.56],[-53.48,49.25],[-53.79,48.52],[-53.09,48.69],[-52.96,48.16],[-52.65,47.54],[-53.07,46.66],[-53.52,46.62],[-54.18,46.81],[-53.96,47.63],[-54.24,47.75],[-55.4,46.88],[-56.0,46.92],[-55.29,47.39],[-56.25,47.63],[-57.33,47.57],[-59.27,47.6],[-59.42,47.9],[-58.8,48.25],[-59.23,48.52],[-58.39,49.13],[-57.36,50.72],[-56.74,51.29],[-55.87,51.63],[-55.41,51.59],[-55.6,51.32],[-56.13,50.69]]]},{"type":"Polygon","coordinates":[[[-132.71,54.04],[-132.71,54.04],[-132.71,54.04],[-132.71,54.04],[-131.75,54.12],[-132.05,52.98],[-131.18,52.18],[-131.58,52.18],[-132.18,52.64],[-132.55,53.1],[-133.05,53.41],[-133.24,53.85],[-133.18,54.17],[-132.71,54.04]]]},{"type":"Polygon","coordinates":[[[143.65,50.75],[144.65,48.98],[143.17,49.31],[142.56,47.86],[143.53,46.84],[143.51,46.14],[142.75,46.74],[142.09,45.97],[141.91,46.81],[142.02,47.78],[141.9,48.86],[142.14,49.62],[142.18,50.95],[141.59,51.94],[141.68,53.3],[142.61,53.76],[142.21,54.23],[142.65,54.37],[142.91,53.7],[143.26,52.74],[143.24,51.76],[143.65,50.75]]]},{"type":"Polygon","coordinates":[[[-6.79,52.26],[-8.56,51.67],[-9.98,51.82],[-9.17,52.86],[-9.69,53.88],[-8.33,54.66],[-7.57,55.13],[-6.73,55.17],[-5.66,54.55],[-6.2,53.87],[-6.03,53.15],[-6.79,52.26]]]},{"type":"Polygon","coordinates":[[[12.69,55.61],[12.09,54.8],[11.04,55.36],[10.9,55.78],[12.37,56.11],[12.69,55.61]]]},{"type":"Polygon","coordinates":[[[-153.01,57.12],[-154.01,56.73],[-154.52,56.99],[-154.67,57.46],[-153.76,57.82],[-153.23,57.97],[-152.56,57.9],[-152.14,57.59],[-153.01,57.12]]]},{"type":"Polygon","coordinates":[[[-3.01,58.63],[-4.07,57.55],[-3.06,57.69],[-1.96,57.68],[-2.22,56.87],[-3.12,55.97],[-2.09,55.91],[-1.11,54.62],[-0.43,54.46],[0.18,53.33],[0.47,52.93],[1.68,52.74],[1.56,52.1],[1.05,51.81],[1.45,51.29],[0.55,50.77],[-0.79,50.77],[-2.49,50.5],[-2.96,50.7],[-3.62,50.23],[-4.54,50.34],[-5.25,49.96],[-5.78,50.16],[-4.31,51.21],[-3.41,51.43],[-4.98,51.59],[-5.27,51.99],[-4.22,52.3],[-4.77,52.84],[-4.58,53.5],[-3.09,53.4],[-2.95,53.98],[-3.63,54.62],[-4.84,54.79],[-5.08,55.06],[-4.72,55.51],[-5.05,55.78],[-5.59,55.31],[-5.64,56.28],[-6.15,56.79],[-5.79,57.82],[-5.01,58.63],[-4.21,58.55],[-3.01,58.63]]]},{"type":"Polygon","coordinates":[[[-165.58,59.91],[-166.19,59.75],[-166.85,59.94],[-167.46,60.21],[-166.47,60.38],[-165.67,60.29],[-165.58,59.91]]]},{"type":"Polygon","coordinates":[[[-79.27,62.16],[-79.66,61.63],[-80.1,61.72],[-80.36,62.02],[-80.32,62.09],[-79.93,62.39],[-79.52,62.36],[-79.27,62.16]]]},{"type":"Polygon","coordinates":[[[-81.9,62.71],[-83.07,62.16],[-83.77,62.18],[-83.99,62.45],[-83.25,62.91],[-81.88,62.9],[-81.9,62.71]]]},{"type":"Polygon","coordinates":[[[-171.73,63.78],[-171.11,63.59],[-170.49,63.69],[-169.68,63.43],[-168.69,63.3],[-168.77,63.19],[-169.53,62.98],[-170.29,63.19],[-170.67,63.38],[-171.55,63.32],[-171.79,63.41],[-171.73,63.78]]]},{"type":"Polygon","coordinates":[[[-85.16,65.66],[-84.98,65.22],[-84.46,65.37],[-83.88,65.11],[-82.79,64.77],[-81.64,64.46],[-81.55,63.98],[-80.82,64.06],[-80.1,63.73],[-80.99,63.41],[-82.55,63.65],[-83.11,64.1],[-84.1,63.57],[-85.52,63.05],[-85.87,63.64],[-87.22,63.54],[-86.35,64.04],[-86.22,64.82],[-85.88,65.74],[-85.16,65.66]]]},{"type":"Polygon","coordinates":[[[-14.51,66.46],[-14.74,65.81],[-13.61,65.13],[-14.91,64.36],[-17.79,63.68],[-18.66,63.5],[-19.97,63.64],[-22.76,63.96],[-21.78,64.4],[-23.96,64.89],[-22.18,65.08],[-22.23,65.38],[-24.33,65.61],[-23.65,66.26],[-22.13,66.41],[-20.58,65.73],[-19.06,66.28],[-17.8,65.99],[-16.17,66.53],[-14.51,66.46]]]},{"type":"Polygon","coordinates":[[[-75.87,67.15],[-76.99,67.1],[-77.24,67.59],[-76.81,68.15],[-75.9,68.29],[-75.11,68.01],[-75.1,67.58],[-75.22,67.44],[-75.87,67.15]]]},{"type":"Polygon","coordinates":[[[-175.01,66.58],[-174.34,66.34],[-174.57,67.06],[-171.86,66.91],[-169.9,65.98],[-170.89,65.54],[-172.53,65.44],[-172.56,64.46],[-172.96,64.25],[-173.89,64.28],[-174.65,64.63],[-175.98,64.92],[-176.21,65.36],[-177.22,65.52],[-178.36,65.39],[-178.9,65.74],[-178.69,66.11],[-179.88,65.87],[-179.43,65.4],[-180,64.98],[-180,68.96],[-177.55,68.2],[-174.93,67.21],[-175.01,66.58]]]},{"type":"Polygon","coordinates":[[[-95.65,69.11],[-96.27,68.76],[-97.62,69.06],[-98.43,68.95],[-99.8,69.4],[-98.92,69.71],[-98.22,70.14],[-97.16,69.86],[-96.56,69.68],[-96.26,69.49],[-95.65,69.11]]]},{"type":"Polygon","coordinates":[[[180,70.83],[178.9,70.78],[178.73,71.1],[180,71.52],[180,70.83]]]},{"type":"Polygon","coordinates":[[[-178.69,70.89],[-180,70.83],[-180,71.52],[-179.87,71.56],[-179.02,71.56],[-177.58,71.27],[-177.66,71.13],[-178.69,70.89]]]},{"type":"Polygon","coordinates":[[[-90.55,69.5],[-90.55,68.48],[-89.22,69.26],[-88.02,68.62],[-88.32,67.87],[-87.35,67.2],[-86.31,67.92],[-85.58,68.78],[-85.52,69.88],[-84.1,69.81],[-82.62,69.66],[-81.28,69.16],[-81.22,68.67],[-81.96,68.13],[-81.26,67.6],[-81.39,67.11],[-83.34,66.41],[-84.74,66.26],[-85.77,66.56],[-86.07,66.06],[-87.03,65.21],[-87.32,64.78],[-88.48,64.1],[-89.91,64.03],[-90.7,63.61],[-90.77,62.96],[-91.93,62.84],[-93.16,62.02],[-94.24,60.9],[-94.63,60.11],[-94.68,58.95],[-93.22,58.78],[-92.76,57.85],[-92.3,57.09],[-90.9,57.28],[-89.04,56.85],[-88.04,56.47],[-87.32,56.0],[-86.07,55.72],[-85.01,55.3],[-83.36,55.24],[-82.27,55.15],[-82.44,54.28],[-82.12,53.28],[-81.4,52.16],[-79.91,51.21],[-79.14,51.53],[-78.6,52.56],[-79.12,54.14],[-79.83,54.67],[-78.23,55.14],[-77.1,55.84],[-76.54,56.53],[-76.62,57.2],[-77.3,58.05],[-78.52,58.8],[-77.34,59.85],[-77.77,60.76],[-78.11,62.32],[-77.41,62.55],[-75.7,62.28],[-74.67,62.18],[-73.84,62.44],[-72.91,62.11],[-71.68,61.53],[-71.37,61.14],[-69.59,61.06],[-69.62,60.22],[-69.29,58.96],[-68.37,58.8],[-67.65,58.21],[-66.2,58.77],[-65.25,59.87],[-64.58,60.34],[-63.8,59.44],[-62.5,58.17],[-61.4,56.97],[-61.8,56.34],[-60.47,55.78],[-59.57,55.2],[-57.98,54.95],[-57.33,54.63],[-56.94,53.78],[-56.16,53.65],[-55.76,53.27],[-55.68,52.15],[-56.41,51.77],[-57.13,51.42],[-58.77,51.06],[-60.03,50.24],[-61.72,50.08],[-63.86,50.29],[-65.36,50.3],[-66.4,50.23],[-67.24,49.51],[-68.51,49.07],[-69.95,47.74],[-71.1,46.82],[-70.26,46.99],[-68.65,48.3],[-66.55,49.13],[-65.06,49.23],[-64.17,48.74],[-65.12,48.07],[-64.8,46.99],[-64.47,46.24],[-63.17,45.74],[-61.52,45.88],[-60.52,47.01],[-60.45,46.28],[-59.8,45.92],[-61.04,45.27],[-63.25,44.67],[-64.25,44.27],[-65.36,43.55],[-66.12,43.62],[-66.16,44.47],[-64.43,45.29],[-66.03,45.26],[-67.14,45.14],[-66.96,44.81],[-68.03,44.33],[-69.06,43.98],[-70.12,43.68],[-70.69,43.03],[-70.81,42.87],[-70.83,42.34],[-70.49,41.81],[-70.08,41.78],[-70.18,42.15],[-69.88,41.92],[-69.97,41.64],[-70.64,41.48],[-71.12,41.49],[-71.86,41.32],[-72.29,41.27],[-72.88,41.22],[-73.71,40.93],[-72.24,41.12],[-71.95,40.93],[-73.34,40.63],[-73.98,40.63],[-73.95,40.75],[-74.26,40.47],[-73.96,40.43],[-74.18,39.71],[-74.91,38.94],[-74.98,39.2],[-75.2,39.25],[-75.53,39.5],[-75.32,38.96],[-75.08,38.78],[-75.06,38.4],[-75.38,38.02],[-75.94,37.22],[-76.03,37.26],[-75.72,37.94],[-76.23,38.32],[-76.35,39.15],[-76.54,38.72],[-76.33,38.08],[-76.96,38.23],[-76.3,37.92],[-76.26,36.97],[-75.97,36.9],[-75.87,36.55],[-75.73,35.55],[-76.36,34.81],[-77.4,34.51],[-78.05,33.93],[-78.55,33.86],[-79.06,33.49],[-79.2,33.16],[-80.3,32.51],[-80.86,32.03],[-81.34,31.44],[-81.49,30.73],[-81.31,30.04],[-80.98,29.18],[-80.54,28.47],[-80.53,28.04],[-80.06,26.88],[-80.09,26.21],[-80.13,25.82],[-80.38,25.21],[-80.68,25.08],[-81.17,25.2],[-81.33,25.64],[-81.71,25.87],[-82.24,26.73],[-82.71,27.5],[-82.86,27.89],[-82.65,28.55],[-82.93,29.1],[-83.71,29.94],[-84.1,30.09],[-85.11,29.64],[-85.29,29.69],[-85.77,30.15],[-86.4,30.4],[-87.53,30.27],[-88.42,30.38],[-89.18,30.32],[-89.6,30.18],[-89.41,29.89],[-89.43,29.49],[-89.22,29.29],[-89.41,29.16],[-89.78,29.31],[-90.15,29.12],[-90.88,29.15],[-91.63,29.68],[-92.5,29.55],[-93.23,29.78],[-93.85,29.71],[-94.69,29.48],[-95.6,28.74],[-96.59,28.31],[-97.14,27.83],[-97.37,27.38],[-97.38,26.69],[-97.33,26.21],[-97.14,25.87],[-97.14,25.87],[-97.14,25.87],[-97.53,24.99],[-97.7,24.27],[-97.78,22.93],[-97.87,22.44],[-97.7,21.9],[-97.39,21.41],[-97.19,20.64],[-96.53,19.89],[-96.29,19.32],[-95.9,18.83],[-94.84,18.56],[-94.43,18.14],[-93.55,18.42],[-92.79,18.52],[-92.04,18.7],[-91.41,18.88],[-90.77,19.28],[-90.53,19.87],[-90.45,20.71],[-90.28,21.0],[-89.6,21.26],[-88.54,21.49],[-87.66,21.46],[-87.05,21.54],[-86.81,21.33],[-86.85,20.85],[-87.38,20.26],[-87.62,19.65],[-87.44,19.47],[-87.59,19.04],[-87.84,18.26],[-88.09,18.52],[-88.3,18.5],[-88.3,18.35],[-88.11,18.35],[-88.12,18.08],[-88.29,17.64],[-88.2,17.49],[-88.3,17.13],[-88.24,17.04],[-88.36,16.53],[-88.55,16.27],[-88.73,16.23],[-88.93,15.89],[-88.6,15.71],[-88.52,15.86],[-88.22,15.73],[-88.12,15.69],[-87.9,15.86],[-87.62,15.88],[-87.52,15.8],[-87.37,15.85],[-86.9,15.76],[-86.44,15.78],[-86.12,15.89],[-86.0,16.01],[-85.68,15.95],[-85.44,15.89],[-85.18,15.91],[-84.98,16.0],[-84.53,15.86],[-84.37,15.84],[-84.06,15.65],[-83.77,15.42],[-83.41,15.27],[-83.15,15.0],[-83.23,14.9],[-83.28,14.68],[-83.18,14.31],[-83.41,13.97],[-83.52,13.57],[-83.55,13.13],[-83.5,12.87],[-83.47,12.42],[-83.63,12.32],[-83.72,11.89],[-83.65,11.63],[-83.86,11.37],[-83.81,11.1],[-83.66,10.94],[-83.4,10.4],[-83.02,9.99],[-82.55,9.57],[-82.19,9.21],[-82.21,9.0],[-81.81,8.95],[-81.71,9.03],[-81.44,8.79],[-80.95,8.86],[-80.52,9.11],[-79.91,9.31],[-79.57,9.61],[-79.02,9.55],[-79.06,9.45],[-78.5,9.42],[-78.06,9.25],[-77.73,8.95],[-77.35,8.67],[-76.84,8.64],[-76.09,9.34],[-75.67,9.44],[-75.66,9.77],[-75.48,10.62],[-74.91,11.08],[-74.28,11.1],[-74.2,11.31],[-73.41,11.23],[-72.63,11.73],[-72.24,11.96],[-71.75,12.44],[-71.4,12.38],[-71.14,12.11],[-71.33,11.78],[-71.36,11.54],[-71.95,11.42],[-71.62,10.97],[-71.63,10.45],[-72.07,9.87],[-71.7,9.07],[-71.26,9.14],[-71.04,9.86],[-71.35,10.21],[-71.4,10.97],[-70.16,11.38],[-70.29,11.85],[-69.94,12.16],[-69.58,11.46],[-68.88,11.44],[-68.23,10.89],[-68.19,10.55],[-67.3,10.55],[-66.23,10.65],[-65.66,10.2],[-64.89,10.08],[-64.33,10.39],[-64.32,10.64],[-63.08,10.7],[-61.88,10.72],[-62.73,10.42],[-62.39,9.95],[-61.59,9.87],[-60.83,9.38],[-60.67,8.58],[-60.15,8.6],[-59.76,8.37],[-59.1,8.0],[-58.48,7.35],[-58.45,6.83],[-58.08,6.81],[-57.54,6.32],[-57.15,5.97],[-55.95,5.77],[-55.84,5.95],[-55.03,6.03],[-53.96,5.76],[-53.62,5.65],[-52.88,5.41],[-51.82,4.57],[-51.66,4.16],[-51.32,4.2],[-51.07,3.65],[-50.51,1.9],[-49.97,1.74],[-49.95,1.05],[-50.7,0.22],[-50.39,-0.08],[-48.62,-0.24],[-48.58,-1.24],[-47.82,-0.58],[-46.57,-0.94],[-44.91,-1.55],[-44.42,-2.14],[-44.58,-2.69],[-43.42,-2.38],[-41.47,-2.91],[-39.98,-2.87],[-38.5,-3.7],[-37.22,-4.82],[-36.45,-5.11],[-35.6,-5.15],[-35.24,-5.46],[-34.9,-6.74],[-34.73,-7.34],[-35.13,-9.0],[-35.64,-9.65],[-37.05,-11.04],[-37.68,-12.17],[-38.42,-13.04],[-38.67,-13.06],[-38.95,-13.79],[-38.88,-15.67],[-39.16,-17.21],[-39.27,-17.87],[-39.58,-18.26],[-39.76,-19.6],[-40.77,-20.9],[-40.94,-21.94],[-41.75,-22.37],[-41.99,-22.97],[-43.07,-22.97],[-44.65,-23.35],[-45.35,-23.8],[-46.47,-24.09],[-47.65,-24.89],[-48.5,-25.88],[-48.64,-26.62],[-48.47,-27.18],[-48.66,-28.19],[-48.89,-28.67],[-49.59,-29.22],[-50.7,-30.98],[-51.58,-31.78],[-52.26,-32.25],[-52.71,-33.2],[-53.37,-33.77],[-53.81,-34.4],[-54.94,-34.95],[-55.67,-34.75],[-56.22,-34.86],[-57.14,-34.43],[-57.82,-34.46],[-58.43,-33.91],[-58.5,-34.43],[-57.23,-35.29],[-57.36,-35.98],[-56.74,-36.41],[-56.79,-36.9],[-57.75,-38.18],[-59.23,-38.72],[-61.24,-38.93],[-62.34,-38.83],[-62.13,-39.42],[-62.33,-40.17],[-62.15,-40.68],[-62.75,-41.03],[-63.77,-41.17],[-64.73,-40.8],[-65.12,-41.06],[-64.98,-42.06],[-64.3,-42.36],[-63.76,-42.04],[-63.46,-42.56],[-64.38,-42.87],[-65.18,-43.5],[-65.33,-44.5],[-65.57,-45.04],[-66.51,-45.04],[-67.29,-45.55],[-67.58,-46.3],[-66.6,-47.03],[-65.64,-47.24],[-65.99,-48.13],[-67.17,-48.7],[-67.82,-49.87],[-68.73,-50.26],[-69.14,-50.73],[-68.82,-51.77],[-68.15,-52.35],[-68.57,-52.3],[-69.46,-52.29],[-69.94,-52.54],[-70.85,-52.9],[-71.01,-53.83],[-71.43,-53.86],[-72.56,-53.53],[-73.7,-52.84],[-74.95,-52.26],[-75.26,-51.63],[-74.98,-51.04],[-75.48,-50.38],[-75.61,-48.67],[-75.18,-47.71],[-74.13,-46.94],[-75.64,-46.65],[-74.69,-45.76],[-74.35,-44.1],[-73.24,-44.45],[-72.72,-42.38],[-73.39,-42.12],[-73.7,-43.37],[-74.33,-43.22],[-74.02,-41.79],[-73.68,-39.94],[-73.22,-39.26],[-73.51,-38.28],[-73.59,-37.16],[-73.17,-37.12],[-72.55,-35.51],[-71.86,-33.91],[-71.44,-32.42],[-71.67,-30.92],[-71.37,-30.1],[-71.49,-28.86],[-70.91,-27.64],[-70.72,-25.71],[-70.4,-23.63],[-70.09,-21.39],[-70.16,-19.76],[-70.37,-18.35],[-71.38,-17.77],[-71.46,-17.36],[-73.44,-16.36],[-75.24,-15.27],[-76.01,-14.65],[-76.42,-13.82],[-76.26,-13.53],[-77.11,-12.22],[-78.09,-10.38],[-79.04,-8.39],[-79.45,-7.93],[-79.76,-7.19],[-80.54,-6.54],[-81.25,-6.14],[-80.93,-5.69],[-81.41,-4.74],[-81.1,-4.04],[-80.3,-3.4],[-79.77,-2.66],[-79.99,-2.22],[-80.37,-2.69],[-80.97,-2.25],[-80.76,-1.97],[-80.93,-1.06],[-80.58,-0.91],[-80.4,-0.28],[-80.02,0.36],[-80.09,0.77],[-79.54,0.98],[-78.86,1.38],[-78.99,1.69],[-78.62,1.77],[-78.66,2.27],[-78.43,2.63],[-77.93,2.7],[-77.51,3.33],[-77.13,3.85],[-77.5,4.09],[-77.31,4.67],[-77.53,5.58],[-77.32,5.85],[-77.48,6.69],[-77.88,7.22],[-78.21,7.51],[-78.43,8.05],[-78.18,8.32],[-78.44,8.39],[-78.62,8.72],[-79.12,9.0],[-79.56,8.93],[-79.76,8.58],[-80.16,8.33],[-80.38,8.3],[-80.48,8.09],[-80.0,7.55],[-80.28,7.42],[-80.42,7.27],[-80.89,7.22],[-81.06,7.82],[-81.19,7.65],[-81.52,7.71],[-81.72,8.11],[-82.13,8.18],[-82.39,8.29],[-82.82,8.29],[-82.85,8.07],[-82.97,8.23],[-83.51,8.45],[-83.71,8.66],[-83.6,8.83],[-83.63,9.05],[-83.91,9.29],[-84.3,9.49],[-84.65,9.62],[-84.71,9.91],[-84.98,10.09],[-84.91,9.8],[-85.11,9.56],[-85.34,9.83],[-85.66,9.93],[-85.8,10.13],[-85.79,10.44],[-85.66,10.75],[-85.94,10.9],[-85.71,11.09],[-86.06,11.4],[-86.53,11.81],[-86.75,12.14],[-87.17,12.46],[-87.67,12.91],[-87.56,13.06],[-87.39,12.91],[-87.32,12.98],[-87.49,13.3],[-87.79,13.38],[-87.9,13.15],[-88.48,13.16],[-88.84,13.26],[-89.26,13.46],[-89.81,13.52],[-90.1,13.74],[-90.61,13.91],[-91.23,13.93],[-91.69,14.13],[-92.23,14.54],[-93.36,15.62],[-93.88,15.94],[-94.69,16.2],[-95.25,16.13],[-96.05,15.75],[-96.56,15.65],[-97.26,15.92],[-98.01,16.11],[-98.95,16.57],[-99.7,16.71],[-100.83,17.17],[-101.67,17.65],[-101.92,17.92],[-102.48,17.98],[-103.5,18.29],[-103.92,18.75],[-104.99,19.32],[-105.49,19.95],[-105.73,20.43],[-105.4,20.53],[-105.5,20.82],[-105.27,21.08],[-105.27,21.42],[-105.6,21.87],[-105.69,22.27],[-106.03,22.77],[-106.91,23.77],[-107.92,24.55],[-108.4,25.17],[-109.26,25.58],[-109.44,25.82],[-109.29,26.44],[-109.8,26.68],[-110.39,27.16],[-110.64,27.86],[-111.18,27.94],[-111.76,28.47],[-112.23,28.95],[-112.27,29.27],[-112.81,30.02],[-113.16,30.79],[-113.15,31.17],[-113.87,31.57],[-114.21,31.52],[-114.78,31.8],[-114.94,31.39],[-114.77,30.91],[-114.67,30.16],[-114.33,29.75],[-113.59,29.06],[-113.42,28.83],[-113.27,28.75],[-113.14,28.41],[-112.96,28.43],[-112.76,27.78],[-112.46,27.53],[-112.24,27.17],[-111.62,26.66],[-111.28,25.73],[-110.99,25.29],[-110.71,24.83],[-110.66,24.3],[-110.17,24.27],[-109.77,23.81],[-109.41,23.36],[-109.43,23.19],[-109.85,22.82],[-110.03,22.82],[-110.29,23.43],[-110.95,24.0],[-111.67,24.48],[-112.18,24.74],[-112.15,25.47],[-112.3,26.01],[-112.78,26.32],[-113.46,26.77],[-113.6,26.64],[-113.85,26.9],[-114.47,27.14],[-115.06,27.72],[-114.98,27.8],[-114.57,27.74],[-114.2,28.12],[-114.16,28.57],[-114.93,29.28],[-115.52,29.56],[-115.89,30.18],[-116.26,30.84],[-116.72,31.64],[-117.13,32.54],[-117.3,33.05],[-117.94,33.62],[-118.41,33.74],[-118.52,34.03],[-119.08,34.08],[-119.44,34.35],[-120.37,34.45],[-120.62,34.61],[-120.74,35.16],[-121.71,36.16],[-122.55,37.55],[-122.51,37.78],[-122.95,38.11],[-123.73,38.95],[-123.87,39.77],[-124.4,40.31],[-124.18,41.14],[-124.21,42.0],[-124.53,42.77],[-124.14,43.71],[-123.9,45.52],[-124.08,46.86],[-124.4,47.72],[-124.69,48.18],[-124.57,48.38],[-123.12,48.04],[-122.59,47.1],[-122.34,47.36],[-122.5,48.18],[-122.84,49.0],[-122.97,49.0],[-124.91,49.98],[-125.62,50.42],[-127.44,50.83],[-127.99,51.72],[-127.85,52.33],[-129.13,52.76],[-129.31,53.56],[-130.51,54.29],[-130.54,54.8],[-131.09,55.18],[-131.97,55.5],[-132.25,56.37],[-133.54,57.18],[-134.08,58.12],[-135.04,58.19],[-136.63,58.21],[-137.8,58.5],[-139.87,59.54],[-140.83,59.73],[-142.57,60.08],[-143.96,60.0],[-145.93,60.46],[-147.11,60.88],[-148.22,60.67],[-148.02,59.98],[-148.57,59.91],[-149.73,59.71],[-150.61,59.37],[-151.72,59.16],[-151.86,59.75],[-151.41,60.73],[-150.35,61.03],[-150.62,61.28],[-151.9,60.73],[-152.58,60.06],[-154.02,59.35],[-153.29,58.86],[-154.23,58.15],[-155.31,57.73],[-156.31,57.42],[-156.56,56.98],[-158.12,56.46],[-158.43,55.99],[-159.6,55.57],[-160.29,55.64],[-161.22,55.36],[-162.24,55.02],[-163.07,54.69],[-164.79,54.4],[-164.94,54.57],[-163.85,55.04],[-162.87,55.35],[-161.8,55.9],[-160.56,56.01],[-160.07,56.42],[-158.68,57.02],[-158.46,57.22],[-157.72,57.57],[-157.55,58.33],[-157.04,58.92],[-158.19,58.62],[-158.52,58.79],[-159.06,58.42],[-159.71,58.93],[-159.98,58.57],[-160.36,59.07],[-161.35,58.67],[-161.97,58.67],[-162.05,59.27],[-161.87,59.63],[-162.52,59.99],[-163.82,59.8],[-164.66,60.27],[-165.35,60.51],[-165.35,61.07],[-166.12,61.5],[-165.73,62.08],[-164.92,62.63],[-164.56,63.15],[-163.75,63.22],[-163.07,63.06],[-162.26,63.54],[-161.53,63.46],[-160.77,63.77],[-160.96,64.22],[-161.52,64.4],[-160.78,64.79],[-161.39,64.78],[-162.45,64.56],[-162.76,64.34],[-163.55,64.56],[-164.96,64.45],[-166.43,64.69],[-166.84,65.09],[-168.11,65.67],[-166.71,66.09],[-164.47,66.58],[-163.65,66.58],[-163.79,66.08],[-161.68,66.12],[-162.49,66.74],[-163.72,67.12],[-164.43,67.62],[-165.39,68.04],[-166.76,68.36],[-166.2,68.88],[-164.43,68.92],[-163.17,69.37],[-162.93,69.86],[-161.91,70.33],[-160.93,70.45],[-159.04,70.89],[-158.12,70.82],[-156.58,71.36],[-155.07,71.15],[-154.34,70.7],[-153.9,70.89],[-152.21,70.83],[-152.27,70.6],[-150.74,70.43],[-149.72,70.53],[-147.61,70.21],[-145.69,70.12],[-144.92,69.99],[-143.59,70.15],[-142.07,69.85],[-140.99,69.71],[-139.12,69.47],[-137.55,68.99],[-136.5,68.9],[-135.63,69.32],[-134.41,69.63],[-132.93,69.51],[-131.43,69.94],[-129.79,70.19],[-129.11,69.78],[-128.36,70.01],[-128.14,70.48],[-127.45,70.38],[-125.76,69.48],[-124.42,70.16],[-124.29,69.4],[-123.06,69.56],[-122.68,69.86],[-121.47,69.8],[-119.94,69.38],[-117.6,69.01],[-116.23,68.84],[-115.25,68.91],[-113.9,68.4],[-115.3,67.9],[-113.5,67.69],[-110.8,67.81],[-109.95,67.98],[-108.88,67.38],[-107.79,67.89],[-108.81,68.31],[-108.17,68.65],[-106.95,68.7],[-106.15,68.8],[-105.34,68.56],[-104.34,68.02],[-103.22,68.1],[-101.45,67.65],[-99.9,67.81],[-98.44,67.78],[-98.56,68.4],[-97.67,68.58],[-96.12,68.24],[-96.13,67.29],[-95.49,68.09],[-94.68,68.06],[-94.23,69.07],[-95.3,69.69],[-96.47,70.09],[-96.39,71.19],[-95.21,71.92],[-93.89,71.76],[-92.88,71.32],[-91.52,70.19],[-92.41,69.7],[-90.55,69.5]]]},{"type":"Polygon","coordinates":[[[-114.17,73.12],[-114.67,72.65],[-112.44,72.96],[-111.05,72.45],[-109.92,72.96],[-109.01,72.63],[-108.19,71.65],[-107.69,72.07],[-108.4,73.09],[-107.52,73.24],[-106.52,73.08],[-105.4,72.67],[-104.77,71.7],[-104.46,70.99],[-102.79,70.5],[-100.98,70.02],[-101.09,69.58],[-102.73,69.5],[-102.09,69.12],[-102.43,68.75],[-104.24,68.91],[-105.96,69.18],[-107.12,69.12],[-109,68.78],[-111.97,68.6],[-113.31,68.54],[-113.85,69.01],[-115.22,69.28],[-116.11,69.17],[-117.34,69.96],[-116.67,70.07],[-115.13,70.24],[-113.72,70.19],[-112.42,70.37],[-114.35,70.6],[-116.49,70.52],[-117.9,70.54],[-118.43,70.91],[-116.11,71.31],[-117.66,71.3],[-119.4,71.56],[-118.56,72.31],[-117.87,72.71],[-115.19,73.31],[-114.17,73.12]]]},{"type":"Polygon","coordinates":[[[-104.5,73.42],[-105.38,72.76],[-106.94,73.46],[-106.6,73.6],[-105.26,73.64],[-104.5,73.42]]]},{"type":"Polygon","coordinates":[[[-76.34,73.1],[-76.25,72.83],[-77.31,72.86],[-78.39,72.88],[-79.49,72.74],[-79.78,72.8],[-80.88,73.33],[-80.83,73.69],[-80.35,73.76],[-78.06,73.65],[-76.34,73.1]]]},{"type":"Polygon","coordinates":[[[-86.56,73.16],[-85.77,72.53],[-84.85,73.34],[-82.32,73.75],[-80.6,72.72],[-80.75,72.06],[-78.77,72.35],[-77.82,72.75],[-75.61,72.24],[-74.23,71.77],[-74.1,71.33],[-72.24,71.56],[-71.2,70.92],[-68.79,70.53],[-67.91,70.12],[-66.97,69.19],[-68.81,68.72],[-66.45,68.07],[-64.86,67.85],[-63.42,66.93],[-61.85,66.86],[-62.16,66.16],[-63.92,65.0],[-65.15,65.43],[-66.72,66.39],[-68.02,66.26],[-68.14,65.69],[-67.09,65.11],[-65.73,64.65],[-65.32,64.38],[-64.67,63.39],[-65.01,62.67],[-66.28,62.95],[-68.78,63.75],[-67.37,62.88],[-66.33,62.28],[-66.17,61.93],[-68.88,62.33],[-71.02,62.91],[-72.24,63.4],[-71.89,63.68],[-73.38,64.19],[-74.83,64.68],[-74.82,64.39],[-77.71,64.23],[-78.56,64.57],[-77.9,65.31],[-76.02,65.33],[-73.96,65.45],[-74.29,65.81],[-73.94,66.31],[-72.65,67.28],[-72.93,67.73],[-73.31,68.07],[-74.84,68.55],[-76.87,68.89],[-76.23,69.15],[-77.29,69.77],[-78.17,69.83],[-78.96,70.17],[-79.49,69.87],[-81.31,69.74],[-84.94,69.97],[-87.06,70.26],[-88.68,70.41],[-89.51,70.76],[-88.47,71.22],[-89.89,71.22],[-90.21,72.24],[-89.44,73.13],[-88.41,73.54],[-85.83,73.8],[-86.56,73.16]]]},{"type":"Polygon","coordinates":[[[-100.36,73.84],[-99.16,73.63],[-97.38,73.76],[-97.12,73.47],[-98.05,72.99],[-96.54,72.56],[-96.72,71.66],[-98.36,71.27],[-99.32,71.36],[-100.01,71.74],[-102.5,72.51],[-102.48,72.83],[-100.44,72.71],[-101.54,73.36],[-100.36,73.84]]]},{"type":"Polygon","coordinates":[[[143.6,73.21],[142.09,73.21],[140.04,73.32],[139.86,73.37],[140.81,73.77],[142.06,73.86],[143.48,73.48],[143.6,73.21]]]},{"type":"Polygon","coordinates":[[[-93.2,72.77],[-94.27,72.02],[-95.41,72.06],[-96.03,72.94],[-96.02,73.44],[-95.5,73.86],[-94.5,74.13],[-92.42,74.1],[-90.51,73.86],[-92.0,72.97],[-93.2,72.77]]]},{"type":"Polygon","coordinates":[[[-120.46,71.4],[-123.09,70.9],[-123.62,71.34],[-125.93,71.87],[-125.59,72.19],[-124.81,73.02],[-123.94,73.68],[-124.92,74.29],[-121.54,74.45],[-120.11,74.24],[-117.56,74.19],[-116.58,73.9],[-115.51,73.48],[-116.77,73.22],[-119.22,72.52],[-120.46,71.82],[-120.46,71.4]]]},{"type":"Polygon","coordinates":[[[150.73,75.08],[149.58,74.69],[147.98,74.78],[146.12,75.17],[146.36,75.5],[148.22,75.35],[150.73,75.08]]]},{"type":"Polygon","coordinates":[[[-93.61,74.98],[-94.16,74.59],[-95.61,74.67],[-96.82,74.93],[-96.29,75.38],[-94.85,75.65],[-93.98,75.3],[-93.61,74.98]]]},{"type":"Polygon","coordinates":[[[145.09,75.56],[144.3,74.82],[140.61,74.85],[138.96,74.61],[136.97,75.26],[137.51,75.95],[138.83,76.14],[141.47,76.09],[145.09,75.56]]]},{"type":"Polygon","coordinates":[[[-98.5,76.72],[-97.74,76.26],[-97.7,75.74],[-98.16,75],[-99.81,74.9],[-100.88,75.06],[-100.86,75.64],[-102.5,75.56],[-102.57,76.34],[-101.49,76.31],[-99.98,76.65],[-98.58,76.59],[-98.5,76.72]]]},{"type":"Polygon","coordinates":[[[-108.21,76.2],[-107.82,75.85],[-106.93,76.01],[-105.88,75.97],[-105.7,75.48],[-106.31,75.01],[-109.7,74.85],[-112.22,74.42],[-113.74,74.39],[-113.87,74.72],[-111.79,75.16],[-116.31,75.04],[-117.71,75.22],[-116.35,76.2],[-115.4,76.48],[-112.59,76.14],[-110.81,75.55],[-109.07,75.47],[-110.5,76.43],[-109.58,76.79],[-108.55,76.68],[-108.21,76.2]]]},{"type":"Polygon","coordinates":[[[57.54,70.72],[56.94,70.63],[53.68,70.76],[53.41,71.21],[51.6,71.47],[51.46,72.01],[52.48,72.23],[52.44,72.77],[54.43,73.63],[53.51,73.75],[55.9,74.63],[55.63,75.08],[57.87,75.61],[61.17,76.25],[64.5,76.44],[66.21,76.81],[68.16,76.94],[68.85,76.54],[68.18,76.23],[64.64,75.74],[61.58,75.26],[58.48,74.31],[56.99,73.33],[55.42,72.37],[55.62,71.54],[57.54,70.72]]]},{"type":"Polygon","coordinates":[[[-94.68,77.1],[-93.57,76.78],[-91.61,76.78],[-90.74,76.45],[-90.97,76.07],[-89.82,75.85],[-89.19,75.61],[-87.84,75.57],[-86.38,75.48],[-84.79,75.7],[-82.75,75.78],[-81.13,75.71],[-80.06,75.34],[-79.83,74.92],[-80.46,74.66],[-81.95,74.44],[-83.23,74.56],[-86.1,74.41],[-88.15,74.39],[-89.76,74.52],[-92.42,74.84],[-92.77,75.39],[-92.89,75.88],[-93.89,76.32],[-95.96,76.44],[-97.12,76.75],[-96.75,77.16],[-94.68,77.1]]]},{"type":"Polygon","coordinates":[[[-116.2,77.65],[-116.34,76.88],[-117.11,76.53],[-118.04,76.48],[-119.9,76.05],[-121.5,75.9],[-122.85,76.12],[-122.85,76.12],[-121.16,76.86],[-119.1,77.51],[-117.57,77.5],[-116.2,77.65]]]},{"type":"Polygon","coordinates":[[[106.97,76.97],[107.24,76.48],[108.15,76.72],[111.08,76.71],[113.33,76.22],[114.13,75.85],[113.89,75.33],[112.78,75.03],[110.15,74.48],[109.4,74.18],[110.64,74.04],[112.12,73.79],[113.02,73.98],[113.53,73.34],[113.97,73.59],[115.57,73.75],[118.78,73.59],[119.02,73.12],[123.2,72.97],[123.26,73.74],[125.38,73.56],[126.98,73.57],[128.59,73.04],[129.05,72.4],[128.46,71.98],[129.72,71.19],[131.29,70.79],[132.25,71.84],[133.86,71.39],[135.56,71.66],[137.5,71.35],[138.23,71.63],[139.87,71.49],[139.15,72.42],[140.47,72.85],[149.5,72.2],[150.35,71.61],[152.97,70.84],[157.01,71.03],[159.0,70.87],[159.83,70.45],[159.71,69.72],[160.94,69.44],[162.28,69.64],[164.05,69.67],[165.94,69.47],[167.84,69.58],[169.58,68.69],[170.82,69.01],[170.01,69.65],[170.45,70.1],[173.64,69.82],[175.72,69.88],[178.6,69.4],[180,68.96],[180,64.98],[179.99,64.97],[178.71,64.53],[177.41,64.61],[178.31,64.08],[178.91,63.25],[179.37,62.98],[179.49,62.57],[179.23,62.3],[177.36,62.52],[174.57,61.77],[173.68,61.65],[172.15,60.95],[170.7,60.34],[170.33,59.88],[168.9,60.57],[166.3,59.79],[165.84,60.16],[164.88,59.73],[163.54,59.87],[163.22,59.21],[162.02,58.24],[162.05,57.84],[163.19,57.62],[163.06,56.16],[162.13,56.12],[161.7,55.29],[162.12,54.86],[160.37,54.34],[160.02,53.2],[158.53,52.96],[158.23,51.94],[156.79,51.01],[156.42,51.7],[155.99,53.16],[155.43,55.38],[155.91,56.77],[156.76,57.36],[156.81,57.83],[158.36,58.06],[160.15,59.31],[161.87,60.34],[163.67,61.14],[164.47,62.55],[163.26,62.47],[162.66,61.64],[160.12,60.54],[159.3,61.77],[156.72,61.43],[154.22,59.76],[155.04,59.15],[152.81,58.88],[151.27,58.78],[151.34,59.5],[149.78,59.66],[148.54,59.16],[145.49,59.34],[142.2,59.04],[138.96,57.09],[135.13,54.73],[136.7,54.6],[137.19,53.98],[138.16,53.76],[138.8,54.25],[139.9,54.19],[141.35,53.09],[141.38,52.24],[140.6,51.24],[140.51,50.05],[140.06,48.45],[138.55,47.0],[138.22,46.31],[136.86,45.14],[135.52,43.99],[134.87,43.4],[133.54,42.81],[132.91,42.8],[132.28,43.28],[130.94,42.55],[130.78,42.22],[130.4,42.28],[129.97,41.94],[129.67,41.6],[129.71,40.88],[129.19,40.66],[129.01,40.49],[128.63,40.19],[127.97,40.03],[127.53,39.76],[127.5,39.32],[127.39,39.21],[127.78,39.05],[128.35,38.61],[129.21,37.43],[129.46,36.78],[129.47,35.63],[129.09,35.08],[128.19,34.89],[127.39,34.48],[126.49,34.39],[126.37,34.93],[126.56,35.68],[126.12,36.73],[126.86,36.89],[126.17,37.75],[125.69,37.94],[125.57,37.75],[125.28,37.67],[125.24,37.86],[124.98,37.95],[124.71,38.11],[124.99,38.55],[125.22,38.67],[125.13,38.85],[125.39,39.39],[125.32,39.55],[124.74,39.66],[124.27,39.93],[122.87,39.64],[122.13,39.17],[121.05,38.9],[121.59,39.36],[121.38,39.75],[122.17,40.42],[121.64,40.95],[120.77,40.59],[119.64,39.9],[119.02,39.25],[118.04,39.2],[117.53,38.74],[118.06,38.06],[118.88,37.9],[118.91,37.45],[119.7,37.16],[120.82,37.87],[121.71,37.48],[122.36,37.45],[122.52,36.93],[121.1,36.65],[120.64,36.11],[119.66,35.61],[119.15,34.91],[120.23,34.36],[120.62,33.38],[121.23,32.46],[121.91,31.69],[121.89,30.95],[121.26,30.68],[121.5,30.14],[122.09,29.83],[121.94,29.02],[121.68,28.23],[121.13,28.14],[120.4,27.05],[119.59,25.74],[118.66,24.55],[117.28,23.62],[115.89,22.78],[114.76,22.67],[114.15,22.22],[113.81,22.55],[113.24,22.05],[111.84,21.55],[110.79,21.4],[110.44,20.34],[109.89,20.28],[109.63,21.01],[109.86,21.4],[108.52,21.72],[108.05,21.55],[106.72,20.7],[105.88,19.75],[105.66,19.06],[106.43,18.0],[107.36,16.7],[108.27,16.08],[108.88,15.28],[109.34,13.43],[109.2,11.67],[108.37,11.01],[107.22,10.36],[106.41,9.53],[105.16,8.6],[104.8,9.24],[105.08,9.92],[104.33,10.49],[103.5,10.63],[103.09,11.15],[102.59,12.19],[101.69,12.65],[100.83,12.63],[100.98,13.41],[100.1,13.41],[100.02,12.31],[99.48,10.85],[99.15,9.96],[99.22,9.24],[99.87,9.21],[100.28,8.3],[100.46,7.43],[101.02,6.86],[101.62,6.74],[102.14,6.22],[102.37,6.13],[102.96,5.52],[103.38,4.86],[103.44,4.18],[103.33,3.73],[103.43,3.38],[103.5,2.79],[103.85,2.52],[104.25,1.63],[104.23,1.29],[103.52,1.23],[102.57,1.97],[101.39,2.76],[101.27,3.27],[100.7,3.94],[100.56,4.77],[100.2,5.31],[100.31,6.04],[100.09,6.46],[99.69,6.85],[99.52,7.34],[98.99,7.91],[98.5,8.38],[98.34,7.79],[98.15,8.35],[98.26,8.97],[98.55,9.93],[98.46,10.68],[98.76,11.44],[98.43,12.03],[98.51,13.12],[98.1,13.64],[97.78,14.84],[97.6,16.1],[97.16,16.93],[96.51,16.43],[95.37,15.71],[94.81,15.8],[94.19,16.04],[94.53,17.28],[94.32,18.21],[93.54,19.37],[93.66,19.73],[93.08,19.86],[92.37,20.67],[92.08,21.19],[92.03,21.7],[91.83,22.18],[91.42,22.77],[90.5,22.81],[90.59,22.39],[90.27,21.84],[89.85,22.04],[89.7,21.86],[89.42,21.97],[89.03,22.06],[88.89,21.69],[88.21,21.7],[86.98,21.5],[87.03,20.74],[86.5,20.15],[85.06,19.48],[83.94,18.3],[83.19,17.67],[82.19,17.02],[82.19,16.56],[81.69,16.31],[80.79,15.95],[80.32,15.9],[80.03,15.14],[80.23,13.84],[80.29,13.01],[79.86,12.06],[79.86,10.36],[79.34,10.31],[78.89,9.55],[79.19,9.22],[78.28,8.93],[77.94,8.25],[77.54,7.97],[76.59,8.9],[76.13,10.3],[75.75,11.31],[75.4,11.78],[74.86,12.74],[74.62,13.99],[74.44,14.62],[73.53,15.99],[73.12,17.93],[72.82,19.21],[72.82,20.42],[72.63,21.36],[71.18,20.76],[70.47,20.88],[69.16,22.09],[69.64,22.45],[69.35,22.84],[68.18,23.69],[67.44,23.94],[67.15,24.66],[66.37,25.43],[64.53,25.24],[62.91,25.22],[61.5,25.08],[59.62,25.38],[58.53,25.61],[57.4,25.74],[56.97,26.97],[56.49,27.14],[55.72,26.96],[54.72,26.48],[53.49,26.81],[52.48,27.58],[51.52,27.87],[50.85,28.81],[50.12,30.15],[49.58,29.99],[48.94,30.32],[48.57,29.93],[47.97,29.98],[48.18,29.53],[48.09,29.31],[48.42,28.55],[48.81,27.69],[49.3,27.46],[49.47,27.11],[50.15,26.69],[50.21,26.28],[50.11,25.94],[50.24,25.61],[50.53,25.33],[50.66,25.0],[50.81,24.75],[50.74,25.48],[51.01,26.01],[51.29,26.11],[51.59,25.8],[51.61,25.22],[51.39,24.63],[51.58,24.25],[51.76,24.29],[51.79,24.02],[52.58,24.18],[53.4,24.15],[54.01,24.12],[54.69,24.8],[55.44,25.44],[56.07,26.06],[56.36,26.4],[56.49,26.31],[56.39,25.9],[56.26,25.71],[56.4,24.92],[56.85,24.24],[57.4,23.88],[58.14,23.75],[58.73,23.57],[59.18,22.99],[59.45,22.66],[59.81,22.53],[59.81,22.31],[59.44,21.71],[59.28,21.43],[58.86,21.11],[58.49,20.43],[58.03,20.48],[57.83,20.24],[57.67,19.74],[57.79,19.07],[57.69,18.94],[57.23,18.95],[56.61,18.57],[56.51,18.09],[56.28,17.88],[55.66,17.88],[55.27,17.63],[55.27,17.23],[54.79,16.95],[54.24,17.05],[53.57,16.71],[53.11,16.65],[52.39,16.38],[52.19,15.94],[52.17,15.6],[51.17,15.18],[49.57,14.71],[48.68,14.0],[48.24,13.95],[47.94,14.01],[47.35,13.59],[46.72,13.4],[45.88,13.35],[45.63,13.29],[45.41,13.03],[45.14,12.95],[44.99,12.7],[44.49,12.72],[44.18,12.59],[43.48,12.64],[43.22,13.22],[43.25,13.77],[43.09,14.06],[42.89,14.8],[42.6,15.21],[42.81,15.26],[42.7,15.72],[42.82,15.91],[42.78,16.35],[42.65,16.77],[42.35,17.08],[42.27,17.47],[41.75,17.83],[41.22,18.67],[40.94,19.49],[40.25,20.17],[39.8,20.34],[39.14,21.29],[39.02,21.99],[39.07,22.58],[38.49,23.69],[38.02,24.08],[37.48,24.29],[37.15,24.86],[37.21,25.08],[36.93,25.6],[36.64,25.83],[36.25,26.57],[35.64,27.38],[35.13,28.06],[34.63,28.06],[34.79,28.61],[34.83,28.96],[34.96,29.36],[34.92,29.5],[34.64,29.1],[34.43,28.34],[34.15,27.82],[33.92,27.65],[33.59,27.97],[33.14,28.42],[32.42,29.85],[32.32,29.76],[32.73,28.71],[33.35,27.7],[34.1,26.14],[34.47,25.6],[34.8,25.03],[35.69,23.93],[35.49,23.75],[35.53,23.1],[36.69,22.2],[36.87,22.0],[37.19,21.02],[36.97,20.84],[37.11,19.81],[37.48,18.61],[37.86,18.37],[38.41,18.0],[38.99,16.84],[39.27,15.92],[39.81,15.44],[41.18,14.49],[41.73,13.92],[42.28,13.34],[42.59,13.0],[43.08,12.7],[43.32,12.39],[43.29,11.97],[42.72,11.74],[43.15,11.46],[43.47,11.28],[43.67,10.86],[44.12,10.45],[44.61,10.44],[45.56,10.7],[46.65,10.82],[47.53,11.13],[48.02,11.19],[48.38,11.38],[48.95,11.41],[49.27,11.43],[49.73,11.58],[50.26,11.68],[50.73,12.02],[51.11,12.02],[51.13,11.75],[51.04,11.17],[51.05,10.64],[50.83,10.28],[50.55,9.2],[50.07,8.08],[49.45,6.8],[48.59,5.34],[47.74,4.22],[46.56,2.86],[45.56,2.05],[44.07,1.05],[43.14,0.29],[42.04,-0.92],[41.81,-1.45],[41.59,-1.68],[40.88,-2.08],[40.64,-2.5],[40.26,-2.57],[40.12,-3.28],[39.8,-3.68],[39.6,-4.35],[39.2,-4.68],[38.74,-5.91],[38.8,-6.48],[39.44,-6.84],[39.47,-7.1],[39.19,-7.7],[39.25,-8.01],[39.19,-8.49],[39.54,-9.11],[39.95,-10.1],[40.32,-10.32],[40.48,-10.77],[40.44,-11.76],[40.56,-12.64],[40.6,-14.2],[40.78,-14.69],[40.48,-15.41],[40.09,-16.1],[39.45,-16.72],[38.54,-17.1],[37.41,-17.59],[36.28,-18.66],[35.9,-18.84],[35.2,-19.55],[34.79,-19.78],[34.7,-20.5],[35.18,-21.25],[35.37,-21.84],[35.39,-22.14],[35.56,-22.09],[35.53,-23.07],[35.37,-23.54],[35.61,-23.71],[35.46,-24.12],[35.04,-24.48],[34.22,-24.82],[33.01,-25.36],[32.57,-25.73],[32.66,-26.15],[32.92,-26.22],[32.83,-26.74],[32.58,-27.47],[32.46,-28.3],[32.2,-28.75],[31.52,-29.26],[31.33,-29.4],[30.9,-29.91],[30.62,-30.42],[30.06,-31.14],[28.93,-32.17],[28.22,-32.77],[27.46,-33.23],[26.42,-33.61],[25.91,-33.67],[25.78,-33.94],[25.17,-33.8],[24.68,-33.99],[23.59,-33.79],[22.99,-33.92],[22.57,-33.86],[21.54,-34.26],[20.69,-34.42],[20.07,-34.8],[19.62,-34.82],[19.19,-34.46],[18.86,-34.44],[18.42,-34.0],[18.38,-34.14],[18.24,-33.87],[18.25,-33.28],[17.93,-32.61],[18.25,-32.43],[18.22,-31.66],[17.57,-30.73],[17.06,-29.88],[17.06,-29.88],[16.35,-28.58],[15.6,-27.82],[15.21,-27.09],[14.99,-26.12],[14.74,-25.39],[14.41,-23.85],[14.39,-22.66],[14.26,-22.11],[13.87,-21.7],[13.35,-20.87],[12.83,-19.67],[12.61,-19.05],[11.79,-18.07],[11.73,-17.3],[11.64,-16.67],[11.78,-15.79],[12.12,-14.88],[12.18,-14.45],[12.5,-13.55],[12.74,-13.14],[13.31,-12.48],[13.63,-12.04],[13.74,-11.3],[13.69,-10.73],[13.39,-10.37],[13.12,-9.77],[12.88,-9.17],[12.93,-8.96],[13.24,-8.56],[12.93,-7.6],[12.73,-6.93],[12.23,-6.29],[12.32,-6.1],[12.18,-5.79],[11.92,-5.04],[11.09,-3.98],[10.07,-2.97],[9.41,-2.14],[8.8,-1.11],[8.83,-0.78],[9.05,-0.46],[9.29,0.27],[9.49,1.01],[9.31,1.16],[9.65,2.28],[9.8,3.07],[9.4,3.73],[8.95,3.9],[8.74,4.35],[8.49,4.5],[8.5,4.77],[7.46,4.41],[7.08,4.46],[6.7,4.24],[5.9,4.26],[5.36,4.89],[5.03,5.61],[4.33,6.27],[3.57,6.26],[2.69,6.26],[1.87,6.14],[1.06,5.93],[-0.51,5.34],[-1.06,5.0],[-1.96,4.71],[-2.86,4.99],[-3.31,4.98],[-4.01,5.18],[-4.65,5.17],[-5.83,4.99],[-6.53,4.71],[-7.52,4.34],[-7.71,4.36],[-7.97,4.36],[-9.0,4.83],[-9.91,5.59],[-10.77,6.14],[-11.44,6.79],[-11.71,6.86],[-12.43,7.26],[-12.95,7.8],[-13.12,8.16],[-13.25,8.9],[-13.69,9.49],[-14.07,9.89],[-14.33,10.02],[-14.58,10.21],[-14.69,10.66],[-14.84,10.88],[-15.13,11.04],[-15.66,11.46],[-16.09,11.52],[-16.31,11.81],[-16.31,11.96],[-16.61,12.17],[-16.68,12.38],[-16.84,13.15],[-16.71,13.6],[-17.13,14.37],[-17.62,14.73],[-17.19,14.92],[-16.7,15.62],[-16.46,16.14],[-16.55,16.67],[-16.27,17.17],[-16.15,18.11],[-16.26,19.1],[-16.38,19.59],[-16.28,20.09],[-16.54,20.57],[-17.06,21.0],[-17.02,21.42],[-16.97,21.89],[-16.59,22.16],[-16.26,22.68],[-16.33,23.02],[-15.98,23.72],[-15.43,24.36],[-15.09,24.52],[-14.82,25.1],[-14.8,25.64],[-14.44,26.25],[-13.77,26.62],[-13.14,27.64],[-12.62,28.04],[-11.69,28.15],[-10.9,28.83],[-10.4,29.1],[-9.56,29.93],[-9.81,31.18],[-9.43,32.04],[-9.3,32.56],[-8.66,33.24],[-7.65,33.7],[-6.91,34.11],[-6.24,35.15],[-5.93,35.76],[-5.19,35.76],[-4.59,35.33],[-3.64,35.4],[-2.6,35.18],[-2.17,35.17],[-1.21,35.71],[-0.13,35.89],[0.5,36.3],[1.47,36.61],[3.16,36.78],[4.82,36.87],[5.32,36.72],[6.26,37.11],[7.33,37.12],[7.74,36.89],[8.42,36.95],[9.51,37.35],[10.21,37.23],[10.18,36.72],[11.03,37.09],[11.1,36.9],[10.6,36.41],[10.59,35.95],[10.94,35.7],[10.81,34.83],[10.15,34.33],[10.34,33.79],[10.86,33.77],[11.11,33.29],[11.49,33.14],[12.66,32.79],[13.08,32.88],[13.92,32.71],[15.25,32.27],[15.71,31.38],[16.61,31.18],[18.02,30.76],[19.09,30.27],[19.57,30.53],[20.05,30.99],[19.82,31.75],[20.13,32.24],[20.85,32.71],[21.54,32.84],[22.9,32.64],[23.24,32.19],[23.61,32.19],[23.93,32.02],[24.92,31.9],[25.16,31.57],[26.5,31.59],[27.46,31.32],[28.45,31.03],[28.91,30.87],[29.68,31.19],[30.1,31.47],[30.98,31.56],[31.69,31.43],[31.96,30.93],[32.19,31.26],[32.99,31.02],[33.77,30.97],[34.27,31.22],[34.56,31.55],[34.49,31.61],[34.75,32.07],[34.96,32.83],[35.1,33.08],[35.13,33.09],[35.48,33.91],[35.98,34.61],[36.0,34.64],[35.91,35.41],[36.15,35.82],[35.78,36.28],[36.16,36.65],[35.55,36.57],[34.71,36.8],[34.03,36.22],[32.51,36.11],[31.7,36.64],[30.62,36.68],[30.39,36.26],[29.7,36.14],[28.73,36.68],[27.64,36.66],[27.05,37.65],[26.32,38.21],[26.8,38.99],[26.17,39.46],[27.28,40.42],[28.82,40.46],[29.24,41.22],[31.15,41.09],[32.35,41.74],[33.51,42.02],[35.17,42.04],[36.91,41.34],[38.35,40.95],[39.51,41.1],[40.37,41.01],[41.55,41.54],[41.7,41.96],[41.45,42.65],[40.88,43.01],[40.32,43.13],[39.96,43.44],[38.68,44.28],[37.54,44.66],[36.68,45.24],[37.4,45.4],[38.23,46.24],[37.67,46.64],[39.15,47.04],[39.12,47.26],[38.22,47.1],[37.43,47.02],[36.76,46.7],[35.82,46.65],[34.96,46.27],[35.02,45.65],[35.51,45.41],[36.53,45.47],[36.33,45.11],[35.24,44.94],[33.88,44.36],[33.33,44.56],[33.55,45.03],[32.45,45.33],[32.63,45.52],[33.59,45.85],[33.3,46.08],[31.74,46.33],[31.68,46.71],[30.75,46.58],[30.38,46.03],[29.6,45.29],[29.63,45.04],[29.14,44.82],[28.84,44.91],[28.56,43.71],[28.04,43.29],[27.67,42.58],[28.0,42.01],[28.12,41.62],[28.99,41.3],[28.81,41.05],[27.62,41.0],[27.19,40.69],[26.36,40.15],[26.04,40.62],[26.06,40.82],[25.45,40.85],[24.93,40.95],[23.71,40.69],[24.41,40.13],[23.9,39.96],[23.34,39.96],[22.81,40.48],[22.63,40.26],[22.85,39.66],[23.35,39.19],[22.97,38.97],[23.53,38.51],[24.03,38.22],[24.04,37.66],[23.12,37.92],[23.41,37.41],[22.78,37.31],[23.15,36.42],[22.49,36.41],[21.67,36.85],[21.3,37.65],[21.12,38.31],[20.73,38.77],[20.22,39.34],[20.15,39.63],[19.98,39.7],[19.96,39.92],[19.41,40.25],[19.32,40.73],[19.4,41.41],[19.54,41.72],[19.37,41.88],[19.16,41.96],[18.88,42.28],[18.45,42.48],[17.51,42.85],[16.93,43.21],[16.02,43.51],[15.17,44.24],[15.38,44.32],[14.92,44.74],[14.9,45.08],[14.26,45.23],[13.95,44.8],[13.66,45.14],[13.68,45.48],[13.72,45.5],[13.94,45.59],[13.14,45.74],[12.33,45.38],[12.38,44.89],[12.26,44.6],[12.59,44.09],[13.53,43.59],[14.03,42.76],[15.14,41.96],[15.93,41.96],[16.17,41.74],[15.89,41.54],[16.79,41.18],[17.52,40.88],[18.38,40.36],[18.48,40.17],[18.29,39.81],[17.74,40.28],[16.87,40.44],[16.45,39.8],[17.17,39.42],[17.05,38.9],[16.64,38.84],[16.1,37.99],[15.68,37.91],[15.69,38.21],[15.89,38.75],[16.11,38.96],[15.72,39.54],[15.41,40.05],[15.0,40.17],[14.7,40.6],[14.06,40.79],[13.63,41.19],[12.89,41.25],[12.11,41.7],[11.19,42.36],[10.51,42.93],[10.2,43.92],[9.7,44.04],[8.89,44.37],[8.43,44.23],[7.85,43.77],[7.44,43.69],[6.53,43.13],[4.56,43.4],[3.1,43.08],[2.99,42.47],[3.04,41.89],[2.09,41.23],[0.81,41.01],[0.72,40.68],[0.11,40.12],[-0.28,39.31],[0.11,38.74],[-0.47,38.29],[-0.68,37.64],[-1.44,37.44],[-2.15,36.67],[-3.42,36.66],[-4.37,36.68],[-5.0,36.32],[-5.38,35.95],[-5.87,36.03],[-6.24,36.37],[-6.52,36.94],[-7.45,37.1],[-7.86,36.84],[-8.38,36.98],[-8.9,36.87],[-8.75,37.65],[-8.84,38.27],[-9.29,38.36],[-9.53,38.74],[-9.45,39.39],[-9.05,39.76],[-8.98,40.16],[-8.77,40.76],[-8.79,41.18],[-8.99,41.54],[-9.03,41.88],[-8.98,42.59],[-9.39,43.03],[-7.98,43.75],[-6.75,43.57],[-5.41,43.57],[-4.35,43.4],[-3.52,43.46],[-1.9,43.42],[-1.38,44.02],[-1.19,46.01],[-2.23,47.06],[-2.96,47.57],[-4.49,47.96],[-4.59,48.68],[-3.3,48.9],[-1.62,48.64],[-1.93,49.78],[-0.99,49.35],[1.34,50.13],[1.64,50.95],[2.51,51.15],[3.32,51.35],[3.83,51.62],[4.71,53.09],[6.07,53.51],[6.91,53.48],[7.1,53.69],[7.94,53.75],[8.12,53.53],[8.8,54.02],[8.57,54.4],[8.53,54.96],[8.12,55.52],[8.09,56.54],[8.26,56.81],[8.54,57.11],[9.42,57.17],[9.78,57.45],[10.58,57.73],[10.55,57.22],[10.25,56.89],[10.37,56.61],[10.91,56.46],[10.67,56.08],[10.37,56.19],[9.65,55.47],[9.92,54.98],[9.94,54.6],[10.95,54.36],[10.94,54.01],[11.96,54.2],[12.52,54.47],[13.65,54.08],[14.12,53.76],[14.8,54.05],[16.36,54.51],[17.62,54.85],[18.62,54.68],[18.7,54.44],[19.66,54.43],[19.89,54.87],[21.27,55.19],[21.06,56.03],[21.09,56.78],[21.58,57.41],[22.52,57.75],[23.32,57.01],[24.12,57.03],[24.31,57.79],[24.43,58.38],[24.06,58.26],[23.43,58.61],[23.34,59.19],[24.6,59.47],[25.86,59.61],[26.95,59.45],[27.98,59.48],[29.12,60.03],[28.07,60.5],[26.26,60.42],[24.5,60.06],[22.87,59.85],[22.29,60.39],[21.32,60.72],[21.54,61.71],[21.06,62.61],[21.54,63.19],[22.44,63.82],[24.73,64.9],[25.4,65.11],[25.29,65.53],[23.9,66.01],[22.18,65.72],[21.21,65.03],[21.37,64.41],[19.78,63.61],[17.85,62.75],[17.12,61.34],[17.83,60.64],[18.79,60.08],[17.87,58.95],[16.83,58.72],[16.45,57.04],[15.88,56.1],[14.67,56.2],[14.1,55.41],[12.94,55.36],[12.63,56.31],[11.79,57.44],[11.03,58.86],[10.36,59.47],[8.38,58.31],[7.05,58.08],[5.67,58.59],[5.31,59.66],[4.99,61.97],[5.91,62.61],[8.55,63.45],[10.53,64.49],[12.36,65.88],[14.76,67.81],[16.44,68.56],[19.18,69.82],[21.38,70.26],[23.02,70.2],[24.55,71.03],[26.37,70.99],[28.17,71.19],[31.29,70.45],[30.01,70.19],[31.1,69.56],[32.13,69.91],[33.78,69.3],[36.51,69.06],[40.29,67.93],[41.06,67.46],[41.13,66.79],[40.02,66.27],[38.38,66.0],[33.92,66.76],[33.18,66.63],[34.81,65.9],[34.94,64.41],[36.23,64.11],[37.01,63.85],[37.14,64.33],[36.52,64.78],[37.18,65.14],[39.59,64.52],[40.44,64.76],[39.76,65.5],[42.09,66.48],[43.02,66.42],[43.95,66.07],[44.53,66.76],[43.7,67.35],[44.19,67.95],[43.45,68.57],[46.25,68.25],[46.82,67.69],[45.56,67.57],[45.56,67.01],[46.35,66.67],[47.89,66.88],[48.14,67.52],[50.23,68.0],[53.72,68.86],[54.47,68.81],[53.49,68.2],[54.73,68.1],[55.44,68.44],[57.32,68.47],[58.8,68.88],[59.94,68.28],[61.08,68.94],[60.03,69.52],[60.55,69.85],[63.5,69.55],[64.89,69.23],[68.51,68.09],[69.18,68.62],[68.16,69.14],[68.14,69.36],[66.93,69.45],[67.26,69.93],[66.72,70.71],[66.69,71.03],[68.54,71.93],[69.2,72.84],[69.94,73.04],[72.59,72.78],[72.8,72.22],[71.85,71.41],[72.47,71.09],[72.79,70.39],[72.56,69.02],[73.67,68.41],[73.24,67.74],[71.28,66.32],[72.42,66.17],[72.82,66.53],[73.92,66.79],[74.19,67.28],[75.05,67.76],[74.47,68.33],[74.94,68.99],[73.84,69.07],[73.6,69.63],[74.4,70.63],[73.1,71.45],[74.89,72.12],[74.66,72.83],[75.16,72.86],[75.68,72.3],[75.29,71.34],[76.36,71.15],[75.9,71.87],[77.58,72.27],[79.65,72.32],[81.5,71.75],[80.61,72.58],[80.51,73.65],[82.25,73.85],[84.66,73.81],[86.82,73.94],[86.01,74.46],[87.17,75.12],[88.32,75.14],[90.26,75.64],[92.9,75.77],[93.23,76.05],[95.86,76.14],[96.68,75.92],[98.92,76.45],[100.76,76.43],[101.04,76.86],[101.99,77.29],[104.35,77.7],[106.07,77.37],[104.71,77.13],[106.97,76.97]],[[49.11,41.28],[49.62,40.57],[50.08,40.53],[50.39,40.26],[49.57,40.18],[49.4,39.4],[49.22,39.05],[48.86,38.82],[48.88,38.32],[49.2,37.58],[50.15,37.37],[50.84,36.87],[52.26,36.7],[53.83,36.97],[53.92,37.2],[53.74,37.91],[53.88,38.95],[53.1,39.29],[53.36,39.98],[52.69,40.03],[52.92,40.88],[53.86,40.63],[54.74,40.95],[54.01,41.55],[53.72,42.12],[52.92,41.87],[52.81,41.14],[52.5,41.78],[52.45,42.03],[52.69,42.44],[52.5,42.79],[51.34,43.13],[50.89,44.03],[50.34,44.28],[50.31,44.61],[51.28,44.51],[51.32,45.25],[52.17,45.41],[53.04,45.26],[53.22,46.23],[53.04,46.85],[52.04,46.8],[51.19,47.05],[50.03,46.61],[49.1,46.4],[48.65,45.81],[47.68,45.64],[46.68,44.61],[47.59,43.66],[47.49,42.99],[48.58,41.81],[49.11,41.28]]]},{"type":"Polygon","coordinates":[[[-93.84,77.52],[-94.3,77.49],[-96.17,77.56],[-96.44,77.83],[-94.42,77.82],[-93.72,77.63],[-93.84,77.52]]]},{"type":"Polygon","coordinates":[[[-110.19,77.7],[-112.05,77.41],[-113.53,77.73],[-112.72,78.05],[-111.26,78.15],[-109.85,78.0],[-110.19,77.7]]]},{"type":"Polygon","coordinates":[[[24.72,77.85],[22.49,77.44],[20.73,77.68],[21.42,77.94],[20.81,78.25],[22.88,78.45],[23.28,78.08],[24.72,77.85]]]},{"type":"Polygon","coordinates":[[[-109.66,78.6],[-110.88,78.41],[-112.54,78.41],[-112.53,78.55],[-111.5,78.85],[-110.96,78.8],[-109.66,78.6]]]},{"type":"Polygon","coordinates":[[[-95.83,78.06],[-97.31,77.85],[-98.12,78.08],[-98.55,78.46],[-98.63,78.87],[-97.34,78.83],[-96.75,78.77],[-95.56,78.42],[-95.83,78.06]]]},{"type":"Polygon","coordinates":[[[-100.06,78.32],[-99.67,77.91],[-101.3,78.02],[-102.95,78.34],[-105.18,78.38],[-104.21,78.68],[-105.42,78.92],[-105.49,79.3],[-103.53,79.17],[-100.83,78.8],[-100.06,78.32]]]},{"type":"Polygon","coordinates":[[[105.08,78.31],[99.44,77.92],[101.26,79.23],[102.09,79.35],[102.84,79.28],[105.37,78.71],[105.08,78.31]]]},{"type":"Polygon","coordinates":[[[18.25,79.7],[21.54,78.96],[19.03,78.56],[18.47,77.83],[17.59,77.64],[17.12,76.81],[15.91,76.77],[13.76,77.38],[14.67,77.74],[13.17,78.02],[11.22,78.87],[10.44,79.65],[13.17,80.01],[13.72,79.66],[15.14,79.67],[15.52,80.02],[16.99,80.05],[18.25,79.7]]]},{"type":"Polygon","coordinates":[[[25.45,80.41],[27.41,80.06],[25.92,79.52],[23.02,79.4],[20.08,79.57],[19.9,79.84],[18.46,79.86],[17.37,80.32],[20.46,80.6],[21.91,80.36],[22.92,80.66],[25.45,80.41]]]},{"type":"Polygon","coordinates":[[[51.14,80.55],[49.79,80.42],[48.89,80.34],[48.75,80.18],[47.59,80.01],[46.5,80.25],[47.07,80.56],[44.85,80.59],[46.8,80.77],[48.32,80.78],[48.52,80.51],[49.1,80.75],[50.04,80.92],[51.52,80.7],[51.14,80.55]]]},{"type":"Polygon","coordinates":[[[99.94,78.88],[97.76,78.76],[94.97,79.04],[93.31,79.43],[92.55,80.14],[91.18,80.34],[93.78,81.02],[95.94,81.25],[97.88,80.75],[100.19,79.78],[99.94,78.88]]]},{"type":"Polygon","coordinates":[[[-87.02,79.66],[-85.81,79.34],[-87.19,79.04],[-89.04,78.29],[-90.8,78.22],[-92.88,78.34],[-93.95,78.75],[-93.94,79.11],[-93.15,79.38],[-94.97,79.37],[-96.08,79.71],[-96.71,80.16],[-96.02,80.6],[-95.32,80.91],[-94.3,80.98],[-94.74,81.21],[-92.41,81.26],[-91.13,80.72],[-89.45,80.51],[-87.81,80.32],[-87.02,79.66]]]},{"type":"Polygon","coordinates":[[[-68.5,83.11],[-65.83,83.03],[-63.68,82.9],[-61.85,82.63],[-61.89,82.36],[-64.33,81.93],[-66.75,81.73],[-67.66,81.5],[-65.48,81.51],[-67.84,80.9],[-69.47,80.62],[-71.18,79.8],[-73.24,79.63],[-73.88,79.43],[-76.91,79.32],[-75.53,79.2],[-76.22,79.02],[-75.39,78.53],[-76.34,78.18],[-77.89,77.9],[-78.36,77.51],[-79.76,77.21],[-79.62,76.98],[-77.91,77.02],[-77.89,76.78],[-80.56,76.18],[-83.17,76.45],[-86.11,76.3],[-87.6,76.42],[-89.49,76.47],[-89.62,76.95],[-87.77,77.18],[-88.26,77.9],[-87.65,77.97],[-84.98,77.54],[-86.34,78.18],[-87.96,78.37],[-87.15,78.76],[-85.38,79.0],[-85.09,79.35],[-86.51,79.74],[-86.93,80.25],[-84.2,80.21],[-83.41,80.1],[-81.85,80.46],[-84.1,80.58],[-87.6,80.52],[-89.37,80.86],[-90.2,81.26],[-91.37,81.55],[-91.59,81.89],[-90.1,82.08],[-88.93,82.12],[-86.97,82.28],[-85.5,82.65],[-84.26,82.6],[-83.18,82.32],[-82.42,82.86],[-81.1,83.02],[-79.31,83.13],[-76.25,83.17],[-75.72,83.06],[-72.83,83.23],[-70.67,83.17],[-68.5,83.11]]]},{"type":"Polygon","coordinates":[[[-27.1,83.52],[-20.85,82.73],[-22.69,82.34],[-26.52,82.3],[-31.9,82.2],[-31.4,82.02],[-27.86,82.13],[-24.84,81.79],[-22.9,82.09],[-22.07,81.73],[-23.17,81.15],[-20.62,81.52],[-15.77,81.91],[-12.77,81.72],[-12.21,81.29],[-16.29,80.58],[-16.85,80.35],[-20.05,80.18],[-17.73,80.13],[-18.9,79.4],[-19.7,78.75],[-19.67,77.64],[-18.47,76.99],[-20.04,76.94],[-21.68,76.63],[-19.83,76.1],[-19.6,75.25],[-20.67,75.16],[-19.37,74.3],[-21.59,74.22],[-20.43,73.82],[-20.76,73.46],[-22.17,73.31],[-23.57,73.31],[-22.31,72.63],[-22.3,72.18],[-24.28,72.6],[-24.79,72.33],[-23.44,72.08],[-22.13,71.47],[-21.75,70.66],[-23.54,70.47],[-24.31,70.86],[-25.54,71.43],[-25.2,70.75],[-26.36,70.23],[-23.73,70.18],[-22.35,70.13],[-25.03,69.26],[-27.75,68.47],[-30.67,68.13],[-31.78,68.12],[-32.81,67.74],[-34.2,66.68],[-36.35,65.98],[-37.04,65.94],[-38.38,65.69],[-39.81,65.46],[-40.67,64.84],[-40.68,64.14],[-41.19,63.48],[-42.82,62.68],[-42.42,61.9],[-42.87,61.07],[-43.38,60.1],[-44.79,60.04],[-46.26,60.85],[-48.26,60.86],[-49.23,61.41],[-49.9,62.38],[-51.63,63.63],[-52.14,64.28],[-52.28,65.18],[-53.66,66.1],[-53.3,66.84],[-53.97,67.19],[-52.98,68.36],[-51.48,68.73],[-51.08,69.15],[-50.87,69.93],[-52.01,69.57],[-52.56,69.43],[-53.46,69.28],[-54.68,69.61],[-54.75,70.29],[-54.36,70.82],[-53.43,70.84],[-51.39,70.57],[-53.11,71.2],[-54.0,71.55],[-55,71.41],[-55.83,71.65],[-54.72,72.59],[-55.33,72.96],[-56.12,73.65],[-57.32,74.71],[-58.6,75.1],[-58.59,75.52],[-61.27,76.1],[-63.39,76.18],[-66.06,76.13],[-68.5,76.06],[-69.66,76.38],[-71.4,77.01],[-68.78,77.32],[-66.76,77.38],[-71.04,77.64],[-73.3,78.04],[-73.16,78.43],[-69.37,78.91],[-65.71,79.39],[-65.32,79.76],[-68.02,80.12],[-67.15,80.52],[-63.69,81.21],[-62.23,81.32],[-62.65,81.77],[-60.28,82.03],[-57.21,82.19],[-54.13,82.2],[-53.04,81.89],[-50.39,82.44],[-48.0,82.06],[-46.6,81.99],[-44.52,81.66],[-46.9,82.2],[-46.76,82.63],[-43.41,83.23],[-39.9,83.18],[-38.62,83.55],[-35.09,83.65],[-27.1,83.52]]]}]} \ No newline at end of file diff --git a/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt b/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt index ce450ded..89b560ce 100644 --- a/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt +++ b/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -13,16 +14,19 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Remove import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -34,20 +38,29 @@ import com.bitchat.android.geohash.Geohash import com.bitchat.android.geohash.GeohashChannelLevel import com.bitchat.android.geohash.LocationChannelManager import com.bitchat.android.ui.globe.GlobeColors +import com.bitchat.android.ui.globe.GlobeMapUiState import com.bitchat.android.ui.globe.GlobeState +import com.bitchat.android.ui.globe.GlobeTileRequest +import com.bitchat.android.ui.globe.GlobeTileSelector +import com.bitchat.android.ui.globe.GlobeViewport import com.bitchat.android.ui.globe.GlobeView -import com.bitchat.android.ui.globe.LandData +import com.bitchat.android.ui.globe.StreamedGlobeRepository import com.bitchat.android.ui.theme.BASE_FONT_SIZE import com.bitchat.android.ui.theme.BitchatFontFamily import com.bitchat.android.ui.theme.BitchatTheme -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged class GeohashPickerActivity : OrientationAwareActivity() { companion object { const val EXTRA_INITIAL_GEOHASH = "initial_geohash" const val EXTRA_RESULT_GEOHASH = "result_geohash" + private const val MAP_REQUEST_SETTLE_MS = 100L + private const val OPENSTREETMAP_COPYRIGHT_URL = + "https://www.openstreetmap.org/copyright" } override fun onCreate(savedInstanceState: Bundle?) { @@ -89,6 +102,7 @@ class GeohashPickerActivity : OrientationAwareActivity() { setContent { BitchatTheme { val context = LocalContext.current + val uriHandler = LocalUriHandler.current val scope = rememberCoroutineScope() val globeState = remember { @@ -104,14 +118,87 @@ class GeohashPickerActivity : OrientationAwareActivity() { LaunchedEffect(globeState) { globeState.attach(scope) } - val land by produceState?>(initialValue = null) { - value = withContext(Dispatchers.IO) { LandData.load(context) } + val mapRepository = remember(context.applicationContext) { + StreamedGlobeRepository(context.applicationContext) } - val borders by produceState>(initialValue = emptyList()) { - value = withContext(Dispatchers.IO) { LandData.loadBorders(context) } + DisposableEffect(mapRepository) { + onDispose { mapRepository.close() } } - val cities by produceState>(initialValue = emptyList()) { - value = withContext(Dispatchers.IO) { LandData.loadCities(context) } + var mapUiState by remember { mutableStateOf(GlobeMapUiState()) } + var mapRetryNonce by remember { mutableIntStateOf(0) } + + LaunchedEffect(globeState, mapRepository, mapRetryNonce) { + if (mapUiState.data.oceanPolygons.isEmpty()) { + mapUiState = mapUiState.copy(isLoading = true, hasError = false) + try { + val overview = mapRepository.load( + GlobeTileRequest(detailZoom = 0, detailTiles = emptySet()) + ) + mapUiState = GlobeMapUiState( + data = overview.data, + isLoading = false, + hasError = false + ) + } catch (cancellation: CancellationException) { + throw cancellation + } catch (_: Exception) { + mapUiState = mapUiState.copy( + isLoading = false, + hasError = true + ) + return@LaunchedEffect + } + } + + snapshotFlow { + if (globeState.isInMotion) { + null + } else { + GlobeTileSelector.select( + GlobeViewport( + centerLat = globeState.centerLat.toDouble(), + centerLon = globeState.centerLon.toDouble(), + globeRadiusPx = globeState.globeRadiusPx, + widthPx = globeState.viewportWidthPx, + heightPx = globeState.viewportHeightPx + ) + ) + } + } + .distinctUntilChanged() + .collectLatest { request -> + // A null request is emitted as soon as motion begins. Keeping it + // in the flow (instead of filtering it) immediately cancels any + // obsolete HTTP/decode batch. + if (request == null) { + mapUiState = mapUiState.copy(isLoading = false) + return@collectLatest + } + // Let fast drag/zoom changes settle before starting another batch. + delay(MAP_REQUEST_SETTLE_MS) + mapUiState = mapUiState.copy(isLoading = true, hasError = false) + try { + val result = mapRepository.load(request) { partial -> + mapUiState = GlobeMapUiState( + data = partial.data, + isLoading = true, + hasError = false + ) + } + mapUiState = GlobeMapUiState( + data = result.data, + isLoading = false, + hasError = result.failedTileCount > 0 + ) + } catch (cancellation: CancellationException) { + throw cancellation + } catch (_: Exception) { + mapUiState = mapUiState.copy( + isLoading = false, + hasError = true + ) + } + } } val colorScheme = MaterialTheme.colorScheme @@ -158,18 +245,14 @@ class GeohashPickerActivity : OrientationAwareActivity() { .fillMaxSize() .background(colorScheme.background) ) { - land?.let { rings -> - GlobeView( - state = globeState, - colors = globeColors, - land = rings, - borders = borders, - cities = cities, - labelTypeface = labelTypeface, - labelTypefaceBold = labelTypefaceBold, - modifier = Modifier.fillMaxSize() - ) - } + GlobeView( + state = globeState, + colors = globeColors, + mapData = mapUiState.data, + labelTypeface = labelTypeface, + labelTypefaceBold = labelTypefaceBold, + modifier = Modifier.fillMaxSize() + ) // Floating info pill Surface( @@ -194,6 +277,74 @@ class GeohashPickerActivity : OrientationAwareActivity() { ) } + if ( + (mapUiState.isLoading && !mapUiState.data.hasGeography) || + mapUiState.hasError + ) { + Surface( + modifier = Modifier + .align(Alignment.TopCenter) + .statusBarsPadding() + .padding(top = 146.dp), + color = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f), + shape = RoundedCornerShape(10.dp), + tonalElevation = 2.dp + ) { + Row( + modifier = Modifier.padding( + start = 10.dp, + end = if (mapUiState.hasError) 4.dp else 10.dp, + top = 6.dp, + bottom = 6.dp + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (mapUiState.isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(14.dp), + strokeWidth = 1.5.dp + ) + } + Text( + text = stringResource( + if (mapUiState.hasError) { + R.string.globe_map_load_error + } else { + R.string.globe_map_loading + } + ), + fontSize = 10.sp, + fontFamily = BitchatFontFamily, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (mapUiState.hasError) { + TextButton(onClick = { mapRetryNonce++ }) { + Text( + text = stringResource(R.string.retry), + fontSize = 10.sp, + fontFamily = BitchatFontFamily + ) + } + } + } + } + } + + Text( + text = stringResource(R.string.openstreetmap_attribution), + modifier = Modifier + .align(Alignment.BottomStart) + .navigationBarsPadding() + .clickable { + uriHandler.openUri(OPENSTREETMAP_COPYRIGHT_URL) + } + .padding(horizontal = 10.dp, vertical = 6.dp), + fontSize = 9.sp, + fontFamily = BitchatFontFamily, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.8f) + ) + // Floating bottom controls Column( modifier = Modifier diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt new file mode 100644 index 00000000..65a08ea8 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt @@ -0,0 +1,197 @@ +package com.bitchat.android.ui.globe + +import kotlin.math.acos +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + +/** + * Render-ready geographic data decoded from streamed OpenStreetMap vector tiles. + * + * Coordinates are retained as latitude/longitude so the existing orthographic globe + * projection remains the sole source of screen geometry and visual styling. + */ +data class GlobeMapData( + val oceanPolygons: List = emptyList(), + val borders: List = emptyList(), + val boundaryLabels: List = emptyList(), + val placeLabels: List = emptyList(), + val detailZoom: Int = 0 +) { + val hasGeography: Boolean + get() = oceanPolygons.isNotEmpty() || borders.isNotEmpty() || + boundaryLabels.isNotEmpty() || placeLabels.isNotEmpty() + + companion object { + val EMPTY = GlobeMapData() + } +} + +data class OceanPolygon(val rings: List) + +data class BorderLine( + val ring: GeoRing, + val maritime: Boolean, + val disputed: Boolean, + val adminLevel: Int? +) + +enum class MapLabelKind { + COUNTRY, + STATE, + CAPITAL, + CITY, + TOWN, + VILLAGE, + OTHER +} + +data class MapLabel( + val name: String, + val lat: Float, + val lon: Float, + val kind: MapLabelKind, + val importance: Long, + val projectionTerms: FloatArray = prepareProjectionTerms( + coords = floatArrayOf(lat, lon), + size = 1 + ) +) { + val rank: Int + get() = when { + kind == MapLabelKind.COUNTRY -> 0 + kind == MapLabelKind.STATE -> 3 + kind == MapLabelKind.CAPITAL -> 0 + importance >= 5_000_000L -> 0 + importance >= 1_000_000L -> 1 + importance >= 500_000L -> 2 + importance >= 100_000L -> 3 + kind == MapLabelKind.CITY -> 4 + kind == MapLabelKind.TOWN -> 6 + kind == MapLabelKind.VILLAGE -> 8 + else -> 10 + } + + val isCapital: Boolean get() = kind == MapLabelKind.CAPITAL + val isMegacity: Boolean get() = importance >= 5_000_000L +} + +data class GeoRing( + val coords: FloatArray, + val size: Int, + /** + * Polygon role from MVT winding order. `true` is a water exterior and `false` + * is a land hole inside that water feature; lines and synthetic geometry use null. + */ + val isMvtExterior: Boolean? = null, + /** + * Per-point sin(latitude), cos(latitude), sin(longitude), cos(longitude). + * Preparing this once removes nearly all trigonometry from animated frames. + */ + val projectionTerms: FloatArray = prepareProjectionTerms(coords, size) +) { + /** + * Conservative spherical cap used to reject rings that cannot touch the viewport. + * The streamed z0 ocean tile contains thousands of tiny island rings, most of which + * are far outside a zoomed view. + */ + internal val sphericalBounds: SphericalRingBounds = + prepareSphericalBounds(projectionTerms, size) +} + +internal data class SphericalRingBounds( + val centerX: Float, + val centerY: Float, + val centerZ: Float, + val angularRadius: Float +) { + fun mayIntersectView( + viewCenterX: Float, + viewCenterY: Float, + viewCenterZ: Float, + viewAngularRadius: Float + ): Boolean { + // Large/degenerate rings can enclose the view even when their edge vertices are + // distant, so only cull compact rings. + if (angularRadius >= MAX_CULLABLE_RING_RADIUS_RADIANS) return true + val maximumDistance = + angularRadius + viewAngularRadius + RING_CULL_MARGIN_RADIANS + if (maximumDistance >= Math.PI.toFloat()) return true + val dot = + centerX * viewCenterX + + centerY * viewCenterY + + centerZ * viewCenterZ + return dot >= cos(maximumDistance) + } +} + +internal fun prepareProjectionTerms(coords: FloatArray, size: Int): FloatArray { + val result = FloatArray(size * 4) + for (index in 0 until size) { + val latRadians = Math.toRadians(coords[index * 2].toDouble()) + val lonRadians = Math.toRadians(coords[index * 2 + 1].toDouble()) + result[index * 4] = sin(latRadians).toFloat() + result[index * 4 + 1] = cos(latRadians).toFloat() + result[index * 4 + 2] = sin(lonRadians).toFloat() + result[index * 4 + 3] = cos(lonRadians).toFloat() + } + return result +} + +private fun prepareSphericalBounds( + projectionTerms: FloatArray, + size: Int +): SphericalRingBounds { + if (size <= 0) { + return SphericalRingBounds(0f, 0f, 1f, Math.PI.toFloat()) + } + var sumX = 0.0 + var sumY = 0.0 + var sumZ = 0.0 + for (index in 0 until size) { + val offset = index * 4 + val sinLat = projectionTerms[offset] + val cosLat = projectionTerms[offset + 1] + val sinLon = projectionTerms[offset + 2] + val cosLon = projectionTerms[offset + 3] + sumX += cosLat * cosLon + sumY += cosLat * sinLon + sumZ += sinLat + } + val length = sqrt(sumX * sumX + sumY * sumY + sumZ * sumZ) + if (length < 1e-6) { + return SphericalRingBounds(0f, 0f, 1f, Math.PI.toFloat()) + } + val centerX = (sumX / length).toFloat() + val centerY = (sumY / length).toFloat() + val centerZ = (sumZ / length).toFloat() + var angularRadius = 0f + for (index in 0 until size) { + val offset = index * 4 + val pointX = projectionTerms[offset + 1] * projectionTerms[offset + 3] + val pointY = projectionTerms[offset + 1] * projectionTerms[offset + 2] + val pointZ = projectionTerms[offset] + val dot = ( + centerX * pointX + + centerY * pointY + + centerZ * pointZ + ).coerceIn(-1f, 1f) + angularRadius = maxOf(angularRadius, acos(dot)) + } + return SphericalRingBounds(centerX, centerY, centerZ, angularRadius) +} + +private const val MAX_CULLABLE_RING_RADIUS_RADIANS = 1.2f +private const val RING_CULL_MARGIN_RADIANS = 0.035f + +data class GlobeMapLoadResult( + val data: GlobeMapData, + val requestedTileCount: Int, + val failedTileCount: Int +) + +data class GlobeMapUiState( + val data: GlobeMapData = GlobeMapData.EMPTY, + val isLoading: Boolean = false, + val hasError: Boolean = false +) diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt index 6c62d0ef..a2649fa7 100644 --- a/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt @@ -47,6 +47,8 @@ class GlobeState( internal var baseRadiusPx by mutableFloatStateOf(0f) internal var screenMinPx by mutableFloatStateOf(0f) + internal var viewportWidthPx by mutableIntStateOf(0) + internal var viewportHeightPx by mutableIntStateOf(0) private var scope: CoroutineScope? = null private var animJob: Job? = null @@ -68,10 +70,24 @@ class GlobeState( this.scope = scope } - fun setViewport(baseRadiusPx: Float, screenMinPx: Float) { - if (baseRadiusPx <= 0f || screenMinPx <= 0f) return + fun setViewport( + baseRadiusPx: Float, + screenMinPx: Float, + widthPx: Int, + heightPx: Int + ) { + if ( + baseRadiusPx <= 0f || + screenMinPx <= 0f || + widthPx <= 0 || + heightPx <= 0 + ) { + return + } this.baseRadiusPx = baseRadiusPx this.screenMinPx = screenMinPx + this.viewportWidthPx = widthPx + this.viewportHeightPx = heightPx syncSelection() } diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeTileSelector.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeTileSelector.kt new file mode 100644 index 00000000..6371601d --- /dev/null +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeTileSelector.kt @@ -0,0 +1,232 @@ +package com.bitchat.android.ui.globe + +import kotlin.math.PI +import kotlin.math.atan +import kotlin.math.floor +import kotlin.math.ln +import kotlin.math.min +import kotlin.math.sinh +import kotlin.math.tan + +data class GlobeViewport( + val centerLat: Double, + val centerLon: Double, + val globeRadiusPx: Float, + val widthPx: Int, + val heightPx: Int +) + +data class GlobeTileKey(val zoom: Int, val x: Int, val y: Int) { + init { + require(zoom in 0..GlobeTileSelector.MAX_TILE_ZOOM) + val dimension = 1 shl zoom + require(x in 0 until dimension) + require(y in 0 until dimension) + } +} + +data class GlobeTileRequest( + val detailZoom: Int, + val detailTiles: Set, + /** + * The small center-first subset that should be decoded and displayed before the + * remaining visible tiles. + */ + val priorityTiles: Set = emptySet() +) + +/** + * Converts the visible portion of the custom orthographic globe into a bounded set of + * Web-Mercator XYZ tiles. Sampling screen space avoids fragile latitude/longitude bounding + * boxes around the poles and antimeridian. + */ +object GlobeTileSelector { + const val MAX_TILE_ZOOM = 14 + private const val BASE_DETAIL_ZOOM = 2 + internal const val MAX_VISIBLE_TILES = 36 + private const val SAMPLE_COLUMNS = 7 + private const val SAMPLE_ROWS = 9 + private const val FITTED_GLOBE_RADIUS_FRACTION = 0.44f + + fun select(viewport: GlobeViewport): GlobeTileRequest? { + if ( + viewport.globeRadiusPx <= 0f || + viewport.widthPx <= 0 || + viewport.heightPx <= 0 + ) { + return null + } + + // When the entire sphere fits on screen, z2 is enough for the current simplified + // globe design and costs only sixteen small tiles for complete global coverage. + if ( + viewport.globeRadiusPx * 2f <= viewport.widthPx && + viewport.globeRadiusPx * 2f <= viewport.heightPx + ) { + val tiles = allTilesAtZoom(BASE_DETAIL_ZOOM) + return GlobeTileRequest( + detailZoom = BASE_DETAIL_ZOOM, + detailTiles = tiles, + priorityTiles = priorityTiles(viewport, BASE_DETAIL_ZOOM, tiles) + ) + } + + // Tile detail follows user zoom, not physical display density. The previous + // circumference-based calculation requested z4 near the fitted view on a high-DPI + // phone, even though z2 has more than enough geometry at that visual scale. + val fittedRadius = min(viewport.widthPx, viewport.heightPx) * + FITTED_GLOBE_RADIUS_FRACTION + val zoomFactor = (viewport.globeRadiusPx / fittedRadius).coerceAtLeast(1f) + val scaleZoom = floor( + log2(zoomFactor.toDouble()) + ).toInt() + BASE_DETAIL_ZOOM + var detailZoom = scaleZoom.coerceIn(BASE_DETAIL_ZOOM, MAX_TILE_ZOOM) + var tiles = sampledTiles(viewport, detailZoom) + + while (tiles.size > MAX_VISIBLE_TILES && detailZoom > BASE_DETAIL_ZOOM) { + detailZoom-- + tiles = sampledTiles(viewport, detailZoom) + } + + return GlobeTileRequest( + detailZoom = detailZoom, + detailTiles = tiles, + priorityTiles = priorityTiles(viewport, detailZoom, tiles) + ) + } + + private fun sampledTiles( + viewport: GlobeViewport, + zoom: Int + ): Set { + val keys = linkedSetOf() + val cx = viewport.widthPx / 2.0 + val cy = viewport.heightPx / 2.0 + val radius = viewport.globeRadiusPx.toDouble() + + for (row in 0 until SAMPLE_ROWS) { + val screenY = viewport.heightPx * row.toDouble() / (SAMPLE_ROWS - 1) + for (column in 0 until SAMPLE_COLUMNS) { + val screenX = viewport.widthPx * column.toDouble() / (SAMPLE_COLUMNS - 1) + val location = GlobeMath.unproject( + x = (screenX - cx) / radius, + y = (screenY - cy) / radius, + centerLatDeg = viewport.centerLat, + centerLonDeg = viewport.centerLon + ) ?: continue + addTileAndNeighbors(keys, zoom, location.first, location.second) + } + } + + addTileAndNeighbors( + keys, + zoom, + viewport.centerLat, + viewport.centerLon + ) + return keys + } + + private fun addTileAndNeighbors( + output: MutableSet, + zoom: Int, + latitude: Double, + longitude: Double + ) { + val dimension = 1 shl zoom + val centerX = longitudeToTileX(longitude, zoom) + val centerY = latitudeToTileY(latitude, zoom) + for (dy in -1..1) { + val y = centerY + dy + if (y !in 0 until dimension) continue + for (dx in -1..1) { + val x = floorMod(centerX + dx, dimension) + output.add(GlobeTileKey(zoom, x, y)) + } + } + } + + private fun priorityTiles( + viewport: GlobeViewport, + zoom: Int, + visibleTiles: Set + ): Set { + val dimension = 1 shl zoom + val centerX = longitudeToTileX(viewport.centerLon, zoom) + val centerY = latitudeToTileY(viewport.centerLat, zoom) + val offsets = arrayOf( + 0 to 0, + -1 to 0, + 1 to 0, + 0 to -1, + 0 to 1 + ) + return buildSet(offsets.size) { + for ((dx, dy) in offsets) { + val y = centerY + dy + if (y !in 0 until dimension) continue + val key = GlobeTileKey(zoom, floorMod(centerX + dx, dimension), y) + if (key in visibleTiles) add(key) + } + } + } + + internal fun longitudeToTileX(longitude: Double, zoom: Int): Int { + val dimension = 1 shl zoom + val normalized = GlobeMath.normalizeLon(longitude) + return floor((normalized + 180.0) / 360.0 * dimension) + .toInt() + .coerceIn(0, dimension - 1) + } + + internal fun latitudeToTileY(latitude: Double, zoom: Int): Int { + val dimension = 1 shl zoom + val clamped = latitude.coerceIn(-WEB_MERCATOR_MAX_LAT, WEB_MERCATOR_MAX_LAT) + val radians = Math.toRadians(clamped) + val mercator = ln(tan(radians) + 1.0 / kotlin.math.cos(radians)) + return floor((1.0 - mercator / PI) / 2.0 * dimension) + .toInt() + .coerceIn(0, dimension - 1) + } + + internal fun tilePointToLongitude( + tileX: Int, + localX: Int, + extent: Int, + zoom: Int + ): Double { + val dimension = (1 shl zoom).toDouble() + return (tileX + localX.toDouble() / extent) / dimension * 360.0 - 180.0 + } + + internal fun tilePointToLatitude( + tileY: Int, + localY: Int, + extent: Int, + zoom: Int + ): Double { + val dimension = (1 shl zoom).toDouble() + val worldY = (tileY + localY.toDouble() / extent) / dimension + return Math.toDegrees(atan(sinh(PI * (1.0 - 2.0 * worldY)))) + } + + private fun allTilesAtZoom(zoom: Int): Set { + val dimension = 1 shl zoom + return buildSet(dimension * dimension) { + for (y in 0 until dimension) { + for (x in 0 until dimension) { + add(GlobeTileKey(zoom, x, y)) + } + } + } + } + + private fun floorMod(value: Int, modulus: Int): Int { + val remainder = value % modulus + return if (remainder < 0) remainder + modulus else remainder + } + + private fun log2(value: Double): Double = ln(value) / ln(2.0) + + private const val WEB_MERCATOR_MAX_LAT = 85.05112878 +} diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt index 7066cf92..b8ea5e72 100644 --- a/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt @@ -4,10 +4,8 @@ import android.graphics.Paint import android.graphics.Typeface import android.os.SystemClock import android.view.HapticFeedbackConstants -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box @@ -17,12 +15,9 @@ import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculateZoom import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect @@ -32,6 +27,7 @@ import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChanged @@ -41,6 +37,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.sp +import androidx.core.graphics.withScale import com.bitchat.android.geohash.Geohash import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.first @@ -67,89 +64,115 @@ data class GlobeColors( private class Star(val x: Float, val y: Float, val radius: Float, val alpha: Float) internal data class GlobeFrameDetail( - val graticuleStepDegrees: Double, + val showGraticule: Boolean, val landPointStride: Int, + val minimumLandRingRadiusPx: Float, val showBorders: Boolean, val cityMaxRank: Int?, val showCityLabels: Boolean, + val showStateLabels: Boolean, + val maximumBoundaryLabels: Int?, val showGeohashGrid: Boolean, val showNeighborCells: Boolean ) internal enum class GlobeMotionDetail { FULL, - BALANCED, FAST } +internal enum class MapLabelTransitionPhase { + STABLE, + ENTERING, + EXITING +} + +internal data class AnimatedMapLabel( + val label: MapLabel, + val phase: MapLabelTransitionPhase +) + +internal data class MapLabelTransitionSet( + val labels: List, + val hasChanges: Boolean +) + +private data class MapLabelAnimationKey( + val name: String, + val latitudeBits: Int, + val longitudeBits: Int, + val kind: MapLabelKind +) + +private fun MapLabel.animationKey(): MapLabelAnimationKey = + MapLabelAnimationKey( + name = name, + latitudeBits = lat.toBits(), + longitudeBits = lon.toBits(), + kind = kind + ) + +internal fun buildMapLabelTransition( + previous: List, + current: List +): MapLabelTransitionSet { + val previousKeys = previous.mapTo(HashSet(previous.size)) { it.animationKey() } + val currentKeys = current.mapTo(HashSet(current.size)) { it.animationKey() } + val labels = ArrayList(previous.size + current.size) + current.forEach { label -> + labels += AnimatedMapLabel( + label = label, + phase = if (label.animationKey() in previousKeys) { + MapLabelTransitionPhase.STABLE + } else { + MapLabelTransitionPhase.ENTERING + } + ) + } + previous.forEach { label -> + if (label.animationKey() !in currentKeys) { + labels += AnimatedMapLabel(label, MapLabelTransitionPhase.EXITING) + } + } + return MapLabelTransitionSet( + labels = labels, + hasChanges = labels.any { it.phase != MapLabelTransitionPhase.STABLE } + ) +} + internal fun globeFrameDetail(motionDetail: GlobeMotionDetail): GlobeFrameDetail = when (motionDetail) { GlobeMotionDetail.FULL -> GlobeFrameDetail( - graticuleStepDegrees = 4.0, + showGraticule = true, landPointStride = 1, + minimumLandRingRadiusPx = 0f, showBorders = true, cityMaxRank = null, showCityLabels = true, + showStateLabels = true, + maximumBoundaryLabels = null, showGeohashGrid = true, showNeighborCells = true ) - GlobeMotionDetail.BALANCED -> GlobeFrameDetail( - graticuleStepDegrees = 8.0, - landPointStride = 2, - showBorders = true, - cityMaxRank = 1, - showCityLabels = false, - showGeohashGrid = true, - showNeighborCells = false - ) GlobeMotionDetail.FAST -> GlobeFrameDetail( - graticuleStepDegrees = 10.0, - landPointStride = 2, - showBorders = false, - cityMaxRank = -1, - showCityLabels = false, - showGeohashGrid = false, - showNeighborCells = false + showGraticule = true, + landPointStride = 1, + minimumLandRingRadiusPx = 0f, + showBorders = true, + cityMaxRank = null, + showCityLabels = true, + showStateLabels = true, + maximumBoundaryLabels = null, + showGeohashGrid = true, + showNeighborCells = true ) } -/** - * Adjusts moving-frame detail from measured frame cadence. Hysteresis keeps the renderer - * from oscillating between levels when timings sit near a boundary. - */ -internal fun nextGlobeMotionDetail( - current: GlobeMotionDetail, - averageFrameMillis: Float -): GlobeMotionDetail { - if (!averageFrameMillis.isFinite()) return GlobeMotionDetail.FAST - return when (current) { - GlobeMotionDetail.FULL -> when { - averageFrameMillis > SEVERELY_SLOW_FRAME_MILLIS -> GlobeMotionDetail.FAST - averageFrameMillis > SLOW_FRAME_MILLIS -> GlobeMotionDetail.BALANCED - else -> GlobeMotionDetail.FULL - } - GlobeMotionDetail.BALANCED -> when { - averageFrameMillis > VERY_SLOW_FRAME_MILLIS -> GlobeMotionDetail.FAST - averageFrameMillis < SMOOTH_FRAME_MILLIS -> GlobeMotionDetail.FULL - else -> GlobeMotionDetail.BALANCED - } - GlobeMotionDetail.FAST -> { - if (averageFrameMillis < RECOVERING_FRAME_MILLIS) { - GlobeMotionDetail.BALANCED - } else { - GlobeMotionDetail.FAST - } - } - } -} - @Composable fun GlobeView( state: GlobeState, colors: GlobeColors, - land: List, - borders: List, - cities: List, + mapData: GlobeMapData, labelTypeface: Typeface?, labelTypefaceBold: Typeface?, modifier: Modifier = Modifier @@ -169,10 +192,48 @@ fun GlobeView( } } - val maxRingPoints = remember(land) { land.maxOfOrNull { it.size } ?: 0 } + val maxRingPoints = remember(mapData.oceanPolygons) { + mapData.oceanPolygons.maxOfOrNull { polygon -> + polygon.rings.maxOfOrNull { it.size } ?: 0 + } ?: 0 + } val scratch = remember(maxRingPoints) { FloatArray(maxOf(1, maxRingPoints) * 3) } - val maxBorderPoints = remember(borders) { borders.maxOfOrNull { it.size } ?: 0 } - val borderScratch = remember(maxBorderPoints) { FloatArray(maxOf(1, maxBorderPoints) * 3) } + val borderScratch = remember { FloatArray(3) } + val borderPaths = remember { BorderPaths() } + val fullGraticule = remember { prepareGraticule(4.0) } + val placeTransitionProgress = remember { Animatable(1f) } + val currentPlaceLabels = remember { mutableStateOf(emptyList()) } + val animatedPlaceLabels = remember { + mutableStateOf(emptyList()) + } + val placeLabelKeys = remember(mapData.placeLabels) { + mapData.placeLabels.map { it.animationKey() } + } + + LaunchedEffect(placeLabelKeys) { + val nextLabels = mapData.placeLabels + val transition = buildMapLabelTransition( + previous = currentPlaceLabels.value, + current = nextLabels + ) + currentPlaceLabels.value = nextLabels + animatedPlaceLabels.value = transition.labels + if (transition.hasChanges) { + placeTransitionProgress.snapTo(0f) + placeTransitionProgress.animateTo( + targetValue = 1f, + animationSpec = tween( + durationMillis = PLACE_LABEL_TRANSITION_MILLIS, + easing = LinearOutSlowInEasing + ) + ) + } else { + placeTransitionProgress.snapTo(1f) + } + animatedPlaceLabels.value = nextLabels.map { + AnimatedMapLabel(it, MapLabelTransitionPhase.STABLE) + } + } val labelPaint = remember { Paint(Paint.ANTI_ALIAS_FLAG).apply { textAlign = Paint.Align.CENTER } @@ -184,13 +245,6 @@ fun GlobeView( } } - val pulse by rememberInfiniteTransition(label = "crosshair").animateFloat( - initialValue = 0.45f, - targetValue = 1f, - animationSpec = infiniteRepeatable(tween(1100), RepeatMode.Reverse), - label = "crosshairAlpha" - ) - LaunchedEffect(state) { // Fire intro once the viewport size is known. snapshotFlow { state.baseRadiusPx }.first { it > 0f } @@ -198,9 +252,15 @@ fun GlobeView( } LaunchedEffect(state) { + var lastHapticMillis = 0L snapshotFlow { state.selectedGeohash } .drop(1) .collect { + val now = SystemClock.uptimeMillis() + if (now - lastHapticMillis < MIN_HAPTIC_INTERVAL_MILLIS) { + return@collect + } + lastHapticMillis = now @Suppress("DEPRECATION") view.performHapticFeedback( HapticFeedbackConstants.KEYBOARD_TAP, @@ -209,37 +269,6 @@ fun GlobeView( } } - var motionDetail by remember(state) { mutableStateOf(GlobeMotionDetail.BALANCED) } - LaunchedEffect(state, state.isInMotion) { - if (!state.isInMotion) { - motionDetail = GlobeMotionDetail.FULL - return@LaunchedEffect - } - - // Balanced is a safe first frame; measured cadence then moves detail up or down. - motionDetail = GlobeMotionDetail.BALANCED - var previousFrameNanos = withFrameNanos { it } - var sampledFrameMillis = 0f - var sampledFrameCount = 0 - while (state.isInMotion) { - val frameNanos = withFrameNanos { it } - val frameMillis = ((frameNanos - previousFrameNanos) / 1_000_000f) - .coerceIn(1f, MAX_SAMPLED_FRAME_MILLIS) - previousFrameNanos = frameNanos - sampledFrameMillis += frameMillis - sampledFrameCount++ - - if (sampledFrameCount >= FRAME_SAMPLE_COUNT) { - motionDetail = nextGlobeMotionDetail( - current = motionDetail, - averageFrameMillis = sampledFrameMillis / sampledFrameCount - ) - sampledFrameMillis = 0f - sampledFrameCount = 0 - } - } - } - val labelTextSize = with(density) { 12.5.sp.toPx() } val labelTextSizeSmall = with(density) { 10.sp.toPx() } @@ -260,7 +289,7 @@ fun GlobeView( .matchParentSize() .onSizeChanged { size: IntSize -> val minDim = min(size.width, size.height).toFloat() - state.setViewport(minDim * 0.44f, minDim) + state.setViewport(minDim * 0.44f, minDim, size.width, size.height) } .pointerInput(state) { var lastTapTime = 0L @@ -268,7 +297,6 @@ fun GlobeView( awaitEachGesture { val down = awaitFirstDown() state.cancelAnimations() - state.isInteracting = true val downTime = SystemClock.uptimeMillis() val downPos = down.position var moved = Offset.Zero @@ -293,10 +321,12 @@ fun GlobeView( moved += pan if (!dragStarted && moved.getDistance() >= viewConfiguration.touchSlop) { dragStarted = true + state.isInteracting = true } if (dragStarted) state.rotateBy(pan.x, pan.y) } if (zoomChange != 1f) { + state.isInteracting = true state.zoomBy(zoomChange) } if (!hadMultiplePointers && pressed.size == 1) { @@ -306,7 +336,6 @@ fun GlobeView( event.changes.forEach { if (it.positionChanged()) it.consume() } } - state.isInteracting = false val upTime = SystemClock.uptimeMillis() val isTap = !hadMultiplePointers && !dragStarted && @@ -343,6 +372,7 @@ fun GlobeView( val velocity = velocityTracker.calculateVelocity() state.fling(velocity.x, velocity.y) } + state.isInteracting = false } } ) { @@ -354,6 +384,7 @@ fun GlobeView( val cLat = state.centerLat.toDouble() val cLon = state.centerLon.toDouble() val preparedProjector = GlobeMath.PreparedProjector(cLat, cLon) + val viewBounds = projectedViewBounds(cLat, cLon, cx, cy, r) // Atmosphere glow drawCircle( @@ -368,59 +399,92 @@ fun GlobeView( center = Offset(cx, cy) ) - // Ocean sphere - drawCircle( - brush = Brush.radialGradient( - 0f to colors.oceanCenter, - 0.7f to colors.oceanCenter, - 1f to colors.oceanEdge, - center = Offset(cx - r * 0.32f, cy - r * 0.38f), - radius = r * 1.5f - ), - radius = r, - center = Offset(cx, cy) + val oceanBrush = Brush.radialGradient( + 0f to colors.oceanCenter, + 0.7f to colors.oceanCenter, + 1f to colors.oceanEdge, + center = Offset(cx - r * 0.32f, cy - r * 0.38f), + radius = r * 1.5f + ) + val clip = ClipRect( + -GEOMETRY_CLIP_MARGIN_PX, + -GEOMETRY_CLIP_MARGIN_PX, + size.width + GEOMETRY_CLIP_MARGIN_PX, + size.height + GEOMETRY_CLIP_MARGIN_PX ) - - val clip = ClipRect(-size.width, -size.height, size.width * 2f, size.height * 2f) val frameDetail = globeFrameDetail( - if (state.isInMotion) motionDetail else GlobeMotionDetail.FULL + if (state.isInMotion) GlobeMotionDetail.FAST else GlobeMotionDetail.FULL ) - // Graticule - drawGraticule( - cx, cy, r, cLat, cLon, colors.graticule, clip, - step = frameDetail.graticuleStepDegrees - ) - - // Fill every landmass first. Coastlines are drawn in a separate final pass so a - // large continent fill can never cover an island outline drawn earlier. - val coastlineRuns = ArrayList>>>(land.size) - for (ring in land) { - val runs = drawLandFill( - ring = ring, + val projectedOcean = if (mapData.oceanPolygons.isNotEmpty()) { + buildProjectedOcean( + polygons = mapData.oceanPolygons, scratch = scratch, projector = preparedProjector, cx = cx, cy = cy, r = r, + clip = clip, + pointStride = frameDetail.landPointStride, + minimumRingRadiusPx = frameDetail.minimumLandRingRadiusPx, + centerLat = cLat, + centerLon = cLon, + viewBounds = viewBounds + ) + } else { + null + } + + if (projectedOcean == null) { + // Until the first streamed world tile arrives, keep the sphere present without + // substituting any bundled geography. + drawCircle(brush = oceanBrush, radius = r, center = Offset(cx, cy)) + if (frameDetail.showGraticule) { + drawGraticule( + cx = cx, + cy = cy, + r = r, + projector = preparedProjector, + color = colors.graticule, + clip = clip, + graticule = fullGraticule + ) + } + } else { + // The streamed ocean mask lets the existing palette remain unchanged: land is + // the base sphere and water receives the same radial treatment as before. + drawCircle(color = colors.land, radius = r, center = Offset(cx, cy)) + drawPath(projectedOcean.path, brush = oceanBrush) + if (frameDetail.showGraticule) { + clipPath(projectedOcean.path) { + drawGraticule( + cx = cx, + cy = cy, + r = r, + projector = preparedProjector, + color = colors.graticule, + clip = clip, + graticule = fullGraticule + ) + } + } + } + + // Keep country and state lines stable across touch, drag, and animation frames. + if (frameDetail.showBorders) { + drawBorders( + lines = mapData.borders, + scratch = borderScratch, + paths = borderPaths, + projector = preparedProjector, + cx = cx, + cy = cy, + r = r, colors = colors, clip = clip, - pointStride = if (ring.size >= 64) frameDetail.landPointStride else 1, - centerLat = cLat, - centerLon = cLon + viewBounds = viewBounds ) - if (runs != null) coastlineRuns.add(runs) - } - for (runs in coastlineRuns) { - strokeRuns(runs, cx, cy, r, colors.coastline, 1.4f, clip) - } - - // Country borders are restored when interaction settles. - if (frameDetail.showBorders) { - for (line in borders) { - drawBorderLine(line, borderScratch, preparedProjector, cx, cy, r, colors, clip) - } } // Sphere shading: dark limb + night side for 3D depth @@ -448,18 +512,41 @@ fun GlobeView( center = Offset(cx, cy) ) - // Cities are detail-only; omitting them while moving keeps touch latency predictable. - val cityMaxRank = frameDetail.cityMaxRank - if (cityMaxRank == null || cityMaxRank >= 0) { - drawCities( - cities, state, preparedProjector, cx, cy, r, colors, - labelPaint, haloPaint, labelTypeface, labelTextSizeSmall, density.density, - maxRankOverride = cityMaxRank, - showLabels = frameDetail.showCityLabels + val occupiedLabelRects = ArrayList() + if (frameDetail.showCityLabels) { + drawBoundaryLabels( + mapData.boundaryLabels, + state, + preparedProjector, + cx, + cy, + r, + colors, + labelPaint, + haloPaint, + labelTypefaceBold, + labelTextSizeSmall, + density.density, + occupiedLabelRects, + showStateLabels = frameDetail.showStateLabels, + maximumLabels = frameDetail.maximumBoundaryLabels ) } - // Detailed cells and labels settle into place after the gesture ends. + // Keep place density stable so tap-to-focus does not visibly swap map layers. + val cityMaxRank = frameDetail.cityMaxRank + if (cityMaxRank == null || cityMaxRank >= 0) { + drawPlaces( + animatedPlaceLabels.value, state, preparedProjector, cx, cy, r, colors, + labelPaint, haloPaint, labelTypeface, labelTextSizeSmall, density.density, + maxRankOverride = cityMaxRank, + showLabels = frameDetail.showCityLabels, + occupiedLabelRects = occupiedLabelRects, + transitionProgress = placeTransitionProgress.value + ) + } + + // Keep the selected and neighboring cells continuous throughout globe movement. if (frameDetail.showGeohashGrid && state.selectedGeohash.isNotEmpty()) { drawGeohashGrid( state, cx, cy, r, cLat, cLon, colors, clip, @@ -475,12 +562,12 @@ fun GlobeView( } - // Keep this animation isolated so its pulse does not redraw the globe geometry. + // Keep the crosshair in its own retained node. A decorative infinite pulse caused + // continuous whole-window frames and repeatedly rasterized the full-detail map. Canvas(modifier = Modifier.matchParentSize()) { val cx = size.width / 2f val cy = size.height / 2f - val crossAlpha = if (state.isInMotion) 0.9f else pulse - val crossColor = colors.accent.copy(alpha = crossAlpha) + val crossColor = colors.accent.copy(alpha = 0.9f) val gap = 5 * density.density val len = 9 * density.density val strokeW = 1.6f * density.density @@ -493,77 +580,152 @@ fun GlobeView( } } -private const val FRAME_SAMPLE_COUNT = 8 -private const val MAX_SAMPLED_FRAME_MILLIS = 50f -private const val SMOOTH_FRAME_MILLIS = 18.5f -private const val SLOW_FRAME_MILLIS = 22f -private const val RECOVERING_FRAME_MILLIS = 22f -private const val VERY_SLOW_FRAME_MILLIS = 29f -private const val SEVERELY_SLOW_FRAME_MILLIS = 34f +private const val MIN_HAPTIC_INTERVAL_MILLIS = 80L +private const val PLACE_LABEL_TRANSITION_MILLIS = 180 + +private data class PreparedGraticule( + val latitudeLines: List, + val longitudeLines: List +) + +private fun prepareGraticule(step: Double): PreparedGraticule { + val latitudeLines = ArrayList() + var latitude = -75.0 + while (latitude <= 75.0) { + val coordinates = ArrayList() + var longitude = -180.0 + while (longitude <= 180.0) { + coordinates.add(latitude.toFloat()) + coordinates.add(longitude.toFloat()) + longitude += step + } + latitudeLines.add( + prepareProjectionTerms(coordinates.toFloatArray(), coordinates.size / 2) + ) + latitude += 15.0 + } + + val longitudeLines = ArrayList() + var longitude = -180.0 + while (longitude < 180.0) { + val coordinates = ArrayList() + var lineLatitude = -90.0 + while (lineLatitude <= 90.0) { + coordinates.add(lineLatitude.toFloat()) + coordinates.add(longitude.toFloat()) + lineLatitude += step + } + longitudeLines.add( + prepareProjectionTerms(coordinates.toFloatArray(), coordinates.size / 2) + ) + longitude += 15.0 + } + return PreparedGraticule(latitudeLines, longitudeLines) +} private fun DrawScope.drawGraticule( cx: Float, cy: Float, r: Float, - cLat: Double, - cLon: Double, + projector: GlobeMath.PreparedProjector, color: Color, clip: ClipRect, - step: Double + graticule: PreparedGraticule ) { val path = Path() + val projection = FloatArray(3) fun strokeSegment(x0: Float, y0: Float, x1: Float, y1: Float) { val seg = clipSegment(x0, y0, x1, y1, clip) ?: return path.moveTo(seg.first.first, seg.first.second) path.lineTo(seg.second.first, seg.second.second) } - // latitude lines - var lat = -75.0 - while (lat <= 75.0) { - var prev: GlobeMath.Projection? = null - var lon = -180.0 - while (lon <= 180.0) { - val p = GlobeMath.project(lat, lon, cLat, cLon) - if (p != null && p.cosC > 0.02f) { - val pp = prev - if (pp != null) strokeSegment(cx + pp.x * r, cy + pp.y * r, cx + p.x * r, cy + p.y * r) - prev = p - } else prev = null - lon += step + + fun addLines(lines: List) { + for (line in lines) { + var hasPrevious = false + var previousX = 0f + var previousY = 0f + val pointCount = line.size / 4 + for (index in 0 until pointCount) { + projector.project(line, index * 4, projection, 0) + if (projection[2] > 0.02f) { + val x = cx + projection[0] * r + val y = cy + projection[1] * r + if (hasPrevious) { + strokeSegment(previousX, previousY, x, y) + } + previousX = x + previousY = y + hasPrevious = true + } else { + hasPrevious = false + } + } } - lat += 15.0 - } - // longitude lines - var lon = -180.0 - while (lon < 180.0) { - var prev: GlobeMath.Projection? = null - var la = -90.0 - while (la <= 90.0) { - val p = GlobeMath.project(la, lon, cLat, cLon) - if (p != null && p.cosC > 0.02f) { - val pp = prev - if (pp != null) strokeSegment(cx + pp.x * r, cy + pp.y * r, cx + p.x * r, cy + p.y * r) - prev = p - } else prev = null - la += step - } - lon += 15.0 } + addLines(graticule.latitudeLines) + addLines(graticule.longitudeLines) drawPath(path, color, style = Stroke(width = 1f)) } -internal data class DiscPt(val x: Float, val y: Float, val front: Boolean) +internal data class DiscPt( + val x: Float, + val y: Float, + val front: Boolean, + val depth: Float = if (front) 1f else -1f +) -private fun limbPoint(behind: DiscPt, front: DiscPt): Pair { - var lo = 0f; var hi = 1f - repeat(14) { - val t = (lo + hi) / 2f - val x = behind.x + (front.x - behind.x) * t - val y = behind.y + (front.y - behind.y) * t - if (x * x + y * y < 1f) lo = t else hi = t +private data class ProjectedOcean( + val path: Path +) + +private data class ProjectedViewBounds( + val centerX: Float, + val centerY: Float, + val centerZ: Float, + val angularRadius: Float +) + +private fun projectedViewBounds( + centerLat: Double, + centerLon: Double, + cx: Float, + cy: Float, + r: Float +): ProjectedViewBounds { + val centerLatitudeRadians = Math.toRadians(centerLat) + val centerLongitudeRadians = Math.toRadians(centerLon) + return ProjectedViewBounds( + centerX = ( + kotlin.math.cos(centerLatitudeRadians) * + kotlin.math.cos(centerLongitudeRadians) + ).toFloat(), + centerY = ( + kotlin.math.cos(centerLatitudeRadians) * + kotlin.math.sin(centerLongitudeRadians) + ).toFloat(), + centerZ = kotlin.math.sin(centerLatitudeRadians).toFloat(), + angularRadius = kotlin.math.asin( + (kotlin.math.sqrt(cx * cx + cy * cy) / r).coerceIn(0f, 1f) + ) + ) +} + +internal fun limbPoint(first: DiscPt, second: DiscPt): Pair { + val depthDelta = first.depth - second.depth + val t = if (kotlin.math.abs(depthDelta) > 1e-6f) { + (first.depth / depthDelta).coerceIn(0f, 1f) + } else { + 0.5f + } + val x = first.x + (second.x - first.x) * t + val y = first.y + (second.y - first.y) * t + val length = kotlin.math.sqrt(x * x + y * y) + return if (length > 1e-6f) { + (x / length) to (y / length) + } else { + 0f to -1f } - val t = (lo + hi) / 2f - return (behind.x + (front.x - behind.x) * t) to (behind.y + (front.y - behind.y) * t) } /** @@ -624,8 +786,10 @@ internal fun buildFrontRuns( } // Skia's edge rasterizer loses precision with path coordinates beyond ~32767px, -// which happens at high globe zoom. All geometry is clipped to an expanded -// viewport rect in screen space before being handed to a Path. +// which happens at high globe zoom. Clipping to the visible viewport also keeps +// transient path masks small enough that motion cannot evict the globe or glyphs. +private const val GEOMETRY_CLIP_MARGIN_PX = 4f + private class ClipRect(val left: Float, val top: Float, val right: Float, val bottom: Float) private fun clipPolygon(pts: List>, rect: ClipRect): List> { @@ -702,13 +866,13 @@ private fun clipSegment( * limb and inserting horizon arc steps between consecutive limb points, so the result * approximates (polygon ∩ visible disc) without self-intersecting chords. */ -private fun buildFillPolygon( +internal fun buildFillPolygon( pts: List, cx: Float, cy: Float, r: Float ): List> { + val firstFrontIndex = pts.indexOfFirst { it.front } + if (firstFrontIndex < 0) return emptyList() val out = ArrayList>(pts.size + 128) - var prevLimbAngle: Float? = null - var firstLimbAngle: Float? = null fun addArc(from: Float, to: Float) { var d = to - from @@ -721,26 +885,32 @@ private fun buildFillPolygon( } } - for (p in pts) { - if (p.front) { - out.add((cx + p.x * r) to (cy + p.y * r)) - prevLimbAngle = null - } else { - val len = kotlin.math.sqrt(p.x * p.x + p.y * p.y) - val lx: Float; val ly: Float - if (len > 1e-6f) { lx = p.x / len; ly = p.y / len } else { lx = 0f; ly = -1f } - val ang = kotlin.math.atan2(ly, lx) - prevLimbAngle?.let { addArc(it, ang) } - if (firstLimbAngle == null) firstLimbAngle = ang - out.add((cx + lx * r) to (cy + ly * r)) - prevLimbAngle = ang - } + fun addProjected(point: DiscPt) { + out.add((cx + point.x * r) to (cy + point.y * r)) } - // wrap-around arc if the ring ends and starts on the limb - val lastAng = prevLimbAngle - val firstAng = firstLimbAngle - if (lastAng != null && firstAng != null && pts.isNotEmpty() && !pts[0].front) { - addArc(lastAng, firstAng) + + var previous = pts[firstFrontIndex] + addProjected(previous) + var exitAngle: Float? = null + for (step in 1..pts.size) { + val current = pts[(firstFrontIndex + step) % pts.size] + when { + previous.front && current.front -> addProjected(current) + previous.front && !current.front -> { + val limb = limbPoint(previous, current) + out.add((cx + limb.first * r) to (cy + limb.second * r)) + exitAngle = kotlin.math.atan2(limb.second, limb.first) + } + !previous.front && current.front -> { + val limb = limbPoint(previous, current) + val entryAngle = kotlin.math.atan2(limb.second, limb.first) + exitAngle?.let { addArc(it, entryAngle) } + out.add((cx + limb.first * r) to (cy + limb.second * r)) + addProjected(current) + exitAngle = null + } + } + previous = current } return out } @@ -775,27 +945,18 @@ private fun DrawScope.fillPolygonClipped( internal fun projectedFillNeedsInversion( polygon: List>, - ring: LandData.Ring, + ring: GeoRing, cx: Float, cy: Float, r: Float, centerLat: Double, - centerLon: Double + centerLon: Double, + quickCheck: Boolean = false ): Boolean { // A single center-point check is ambiguous for a large polygon and caused an // occasional whole-disc fill. Compare several visible points with the original // geographic ring, and invert only when the opposite fill wins clearly. - val samples = arrayOf( - 0f to 0f, - -0.5f to 0f, - 0.5f to 0f, - 0f to -0.5f, - 0f to 0.5f, - -0.35f to -0.35f, - 0.35f to -0.35f, - -0.35f to 0.35f, - 0.35f to 0.35f - ) + val samples = if (quickCheck) QUICK_FILL_SAMPLES else FULL_FILL_SAMPLES var normalErrors = 0 var invertedErrors = 0 for ((nx, ny) in samples) { @@ -813,6 +974,24 @@ internal fun projectedFillNeedsInversion( return invertedErrors < normalErrors } +private val QUICK_FILL_SAMPLES = arrayOf( + 0f to 0f, + -0.5f to 0f, + 0.5f to 0f +) + +private val FULL_FILL_SAMPLES = arrayOf( + 0f to 0f, + -0.5f to 0f, + 0.5f to 0f, + 0f to -0.5f, + 0f to 0.5f, + -0.35f to -0.35f, + 0.35f to -0.35f, + -0.35f to 0.35f, + 0.35f to 0.35f +) + internal fun polygonContains( polygon: List>, x: Float, @@ -835,7 +1014,7 @@ internal fun polygonContains( } internal fun ringContainsLocation( - ring: LandData.Ring, + ring: GeoRing, latitude: Double, longitude: Double ): Boolean { @@ -886,38 +1065,280 @@ private fun DrawScope.strokeRuns( } } -private fun DrawScope.drawBorderLine( - line: LandData.Ring, +private class BorderPaths { + val maritime = Path() + val maritimeDisputed = Path() + val country = Path() + val countryDisputed = Path() + val local = Path() + val localDisputed = Path() + + fun reset() { + maritime.reset() + maritimeDisputed.reset() + country.reset() + countryDisputed.reset() + local.reset() + localDisputed.reset() + } + + fun forLine(line: BorderLine): Path = when { + line.maritime && line.disputed -> maritimeDisputed + line.maritime -> maritime + line.adminLevel == 2 && line.disputed -> countryDisputed + line.adminLevel == 2 -> country + line.disputed -> localDisputed + else -> local + } +} + +private fun DrawScope.drawBorders( + lines: List, scratch: FloatArray, + paths: BorderPaths, projector: GlobeMath.PreparedProjector, cx: Float, cy: Float, r: Float, colors: GlobeColors, - clip: ClipRect + clip: ClipRect, + viewBounds: ProjectedViewBounds ) { - val n = line.size - if (n < 2 || n * 3 > scratch.size) return - - var anyFront = false - val pts = ArrayList(n) - var i = 0 - while (i < n) { - projector.project(line.projectionTerms, i * 4, scratch, i * 3) - val x = scratch[i * 3] - val y = scratch[i * 3 + 1] - val cosC = scratch[i * 3 + 2] - val front = cosC > 0.005f - pts.add(DiscPt(x, y, front)) - if (cosC >= 0f) anyFront = true - i++ + paths.reset() + for (line in lines) { + val ring = line.ring + if ( + ring.size < 2 || + !ring.sphericalBounds.mayIntersectView( + viewBounds.centerX, + viewBounds.centerY, + viewBounds.centerZ, + viewBounds.angularRadius + ) + ) { + continue + } + paths.forLine(line).appendProjectedBorder( + ring = ring, + scratch = scratch, + projector = projector, + cx = cx, + cy = cy, + r = r, + clip = clip + ) } - if (!anyFront) return - val runs = buildFrontRuns(pts, closed = false) - strokeRuns(runs, cx, cy, r, colors.border, 1.2f, clip) + drawPath(paths.maritime, colors.coastline, style = Stroke(width = 1.4f)) + drawPath( + paths.maritimeDisputed, + colors.coastline.copy(alpha = colors.coastline.alpha * 0.65f), + style = Stroke(width = 1.4f) + ) + drawPath(paths.country, colors.border, style = Stroke(width = 1.2f)) + drawPath( + paths.countryDisputed, + colors.border.copy(alpha = colors.border.alpha * 0.65f), + style = Stroke(width = 1.2f) + ) + drawPath(paths.local, colors.border, style = Stroke(width = 0.9f)) + drawPath( + paths.localDisputed, + colors.border.copy(alpha = colors.border.alpha * 0.65f), + style = Stroke(width = 0.9f) + ) } -private fun DrawScope.drawCities( - cities: List, +private fun Path.appendProjectedBorder( + ring: GeoRing, + scratch: FloatArray, + projector: GlobeMath.PreparedProjector, + cx: Float, + cy: Float, + r: Float, + clip: ClipRect +) { + projector.project(ring.projectionTerms, 0, scratch, 0) + var previousX = scratch[0] + var previousY = scratch[1] + var previousDepth = scratch[2] + var emittedX = previousX + var emittedY = previousY + var hasEmittedFrontPoint = previousDepth >= 0f + var pathIsOpen = false + val minimumSegmentSquared = MIN_BORDER_SEGMENT_PX * MIN_BORDER_SEGMENT_PX + + for (index in 1 until ring.size) { + projector.project(ring.projectionTerms, index * 4, scratch, 0) + val currentX = scratch[0] + val currentY = scratch[1] + val currentDepth = scratch[2] + val previousFront = previousDepth >= 0f + val currentFront = currentDepth >= 0f + + when { + previousFront && currentFront -> { + if (!hasEmittedFrontPoint) { + emittedX = previousX + emittedY = previousY + hasEmittedFrontPoint = true + } + val dx = (currentX - emittedX) * r + val dy = (currentY - emittedY) * r + if ( + index == ring.size - 1 || + dx * dx + dy * dy >= minimumSegmentSquared + ) { + pathIsOpen = appendClippedSegment( + cx + emittedX * r, + cy + emittedY * r, + cx + currentX * r, + cy + currentY * r, + clip, + connectFromPrevious = pathIsOpen + ) + emittedX = currentX + emittedY = currentY + } + } + previousFront && !currentFront -> { + val limb = limbPoint( + DiscPt(previousX, previousY, front = true, depth = previousDepth), + DiscPt(currentX, currentY, front = false, depth = currentDepth) + ) + if (hasEmittedFrontPoint) { + appendClippedSegment( + cx + emittedX * r, + cy + emittedY * r, + cx + limb.first * r, + cy + limb.second * r, + clip, + connectFromPrevious = pathIsOpen + ) + } + hasEmittedFrontPoint = false + pathIsOpen = false + } + !previousFront && currentFront -> { + val limb = limbPoint( + DiscPt(previousX, previousY, front = false, depth = previousDepth), + DiscPt(currentX, currentY, front = true, depth = currentDepth) + ) + pathIsOpen = appendClippedSegment( + cx + limb.first * r, + cy + limb.second * r, + cx + currentX * r, + cy + currentY * r, + clip, + connectFromPrevious = false + ) + emittedX = currentX + emittedY = currentY + hasEmittedFrontPoint = true + } + } + + previousX = currentX + previousY = currentY + previousDepth = currentDepth + } +} + +private const val MIN_BORDER_SEGMENT_PX = 1.5f + +private fun Path.appendClippedSegment( + x0: Float, + y0: Float, + x1: Float, + y1: Float, + clip: ClipRect, + connectFromPrevious: Boolean +): Boolean { + val firstInside = + x0 in clip.left..clip.right && + y0 in clip.top..clip.bottom + val secondInside = + x1 in clip.left..clip.right && + y1 in clip.top..clip.bottom + if (firstInside && secondInside) { + if (!connectFromPrevious) moveTo(x0, y0) + lineTo(x1, y1) + return true + } + val segment = clipSegment(x0, y0, x1, y1, clip) ?: return false + if (!(connectFromPrevious && firstInside)) { + moveTo(segment.first.first, segment.first.second) + } + lineTo(segment.second.first, segment.second.second) + return secondInside +} + +private fun DrawScope.drawBoundaryLabels( + labels: List, + state: GlobeState, + projector: GlobeMath.PreparedProjector, + cx: Float, + cy: Float, + r: Float, + colors: GlobeColors, + labelPaint: Paint, + haloPaint: Paint, + typeface: Typeface?, + textSize: Float, + density: Float, + occupiedLabelRects: MutableList, + showStateLabels: Boolean, + maximumLabels: Int? +) { + if (labels.isEmpty()) return + val projection = FloatArray(3) + val canvas = drawContext.canvas.nativeCanvas + var drawnLabelCount = 0 + for (label in labels) { + if ( + label.kind == MapLabelKind.STATE && + (!showStateLabels || state.zoom < 6f) + ) { + continue + } + projector.project(label.projectionTerms, 0, projection, 0) + val cosC = projection[2] + if (cosC < 0.1f) continue + val sx = cx + projection[0] * r + val sy = cy + projection[1] * r + if (sx < 0f || sx > size.width || sy < 0f || sy > size.height) continue + + val scaledTextSize = if (label.kind == MapLabelKind.COUNTRY) { + textSize * 1.02f + } else { + textSize * 0.9f + } + configureMapLabelPaints( + labelPaint = labelPaint, + haloPaint = haloPaint, + typeface = typeface, + textSize = scaledTextSize, + textAlign = Paint.Align.CENTER, + colors = colors, + alpha = cosC.coerceIn(0.3f, 1f) + ) + val width = labelPaint.measureText(label.name) + val height = labelPaint.descent() - labelPaint.ascent() + val rect = Rect( + left = sx - width / 2f - 3f * density, + top = sy - height / 2f - 2f * density, + right = sx + width / 2f + 3f * density, + bottom = sy + height / 2f + 2f * density + ) + if (!occupiedLabelRects.reserve(rect)) continue + val baseline = sy - (labelPaint.descent() + labelPaint.ascent()) / 2f + canvas.drawText(label.name, sx, baseline, haloPaint) + canvas.drawText(label.name, sx, baseline, labelPaint) + drawnLabelCount++ + if (maximumLabels != null && drawnLabelCount >= maximumLabels) break + } +} + +private fun DrawScope.drawPlaces( + places: List, state: GlobeState, projector: GlobeMath.PreparedProjector, cx: Float, cy: Float, r: Float, @@ -928,10 +1349,13 @@ private fun DrawScope.drawCities( textSize: Float, density: Float, maxRankOverride: Int?, - showLabels: Boolean + showLabels: Boolean, + occupiedLabelRects: MutableList, + transitionProgress: Float ) { - if (cities.isEmpty()) return + if (places.isEmpty()) return val projection = FloatArray(3) + val progress = transitionProgress.coerceIn(0f, 1f) val zoom = state.zoom val maxRank = maxRankOverride ?: when { zoom < 2f -> 1 @@ -940,106 +1364,227 @@ private fun DrawScope.drawCities( else -> 10 } val canvas = drawContext.canvas.nativeCanvas - for (city in cities) { - if (city.rank > maxRank) continue - projector.project(city.projectionTerms, 0, projection, 0) + for (animatedPlace in places) { + val place = animatedPlace.label + val visibility = when (animatedPlace.phase) { + MapLabelTransitionPhase.STABLE -> 1f + MapLabelTransitionPhase.ENTERING -> progress + MapLabelTransitionPhase.EXITING -> 1f - progress + } + if (visibility <= 0.01f) continue + val transitionScale = when (animatedPlace.phase) { + MapLabelTransitionPhase.STABLE -> 1f + MapLabelTransitionPhase.ENTERING -> 0.84f + 0.16f * progress + MapLabelTransitionPhase.EXITING -> 1f - 0.12f * progress + } + if (place.rank > maxRank) continue + projector.project(place.projectionTerms, 0, projection, 0) val cosC = projection[2] if (cosC < 0.03f) continue val sx = cx + projection[0] * r val sy = cy + projection[1] * r if (sx < -50 || sx > size.width + 50 || sy < -50 || sy > size.height + 50) continue - val alpha = cosC.coerceIn(0.25f, 1f) - val important = city.capital || city.megacity + val alpha = cosC.coerceIn(0.25f, 1f) * visibility + val important = place.isCapital || place.isMegacity val dotRadius = (if (important) 2.6f else 1.8f) * density - val dotColor = if (city.capital) colors.accent.copy(alpha = alpha) + val dotColor = if (place.isCapital) colors.accent.copy(alpha = alpha) else colors.label.copy(alpha = alpha * 0.85f) - drawCircle(dotColor, radius = dotRadius, center = Offset(sx, sy)) + drawCircle( + dotColor, + radius = dotRadius * transitionScale, + center = Offset(sx, sy) + ) if (showLabels && (zoom >= 6f || (important && zoom >= 2.5f))) { - labelPaint.textSize = textSize - labelPaint.typeface = typeface - labelPaint.textAlign = Paint.Align.LEFT - labelPaint.color = android.graphics.Color.argb( - (200 * alpha).toInt(), - (colors.label.red * 255).toInt(), (colors.label.green * 255).toInt(), (colors.label.blue * 255).toInt() - ) - haloPaint.textSize = textSize - haloPaint.typeface = typeface - haloPaint.textAlign = Paint.Align.LEFT - haloPaint.strokeWidth = textSize * 0.16f - haloPaint.color = android.graphics.Color.argb( - (140 * alpha).toInt(), - (colors.labelHalo.red * 255).toInt(), (colors.labelHalo.green * 255).toInt(), (colors.labelHalo.blue * 255).toInt() + configureMapLabelPaints( + labelPaint = labelPaint, + haloPaint = haloPaint, + typeface = typeface, + textSize = textSize, + textAlign = Paint.Align.LEFT, + colors = colors, + alpha = alpha ) val tx = sx + dotRadius + 3 * density val ty = sy - ((labelPaint.descent() + labelPaint.ascent()) / 2f) - canvas.drawText(city.name, tx, ty, haloPaint) - canvas.drawText(city.name, tx, ty, labelPaint) + val width = labelPaint.measureText(place.name) + val rect = Rect( + left = tx - 2f * density, + top = ty + labelPaint.ascent() - 2f * density, + right = tx + width + 2f * density, + bottom = ty + labelPaint.descent() + 2f * density + ) + if (occupiedLabelRects.reserve(rect)) { + canvas.withScale(transitionScale, transitionScale, tx, sy) { + drawText(place.name, tx, ty, haloPaint) + drawText(place.name, tx, ty, labelPaint) + } + } } } labelPaint.textAlign = Paint.Align.CENTER haloPaint.textAlign = Paint.Align.CENTER } -private fun DrawScope.drawLandFill( - ring: LandData.Ring, +private fun configureMapLabelPaints( + labelPaint: Paint, + haloPaint: Paint, + typeface: Typeface?, + textSize: Float, + textAlign: Paint.Align, + colors: GlobeColors, + alpha: Float +) { + labelPaint.textSize = textSize + labelPaint.typeface = typeface + labelPaint.textAlign = textAlign + labelPaint.color = android.graphics.Color.argb( + (200 * alpha).toInt(), + (colors.label.red * 255).toInt(), + (colors.label.green * 255).toInt(), + (colors.label.blue * 255).toInt() + ) + haloPaint.textSize = textSize + haloPaint.typeface = typeface + haloPaint.textAlign = textAlign + haloPaint.strokeWidth = textSize * 0.16f + haloPaint.color = android.graphics.Color.argb( + (140 * alpha).toInt(), + (colors.labelHalo.red * 255).toInt(), + (colors.labelHalo.green * 255).toInt(), + (colors.labelHalo.blue * 255).toInt() + ) +} + +private fun MutableList.reserve(candidate: Rect): Boolean { + if (any { it.overlaps(candidate) }) return false + add(candidate) + return true +} + +private fun buildProjectedOcean( + polygons: List, scratch: FloatArray, projector: GlobeMath.PreparedProjector, - cx: Float, cy: Float, r: Float, - colors: GlobeColors, + cx: Float, + cy: Float, + r: Float, clip: ClipRect, pointStride: Int, + minimumRingRadiusPx: Float, centerLat: Double, - centerLon: Double -): List>>? { - val n = ((ring.size - 1) / pointStride) + 1 - if (n < 3 || n * 3 > scratch.size) return null - - var anyFront = false - var anyBack = false - var i = 0 - while (i < n) { - val sourceIndex = (i * pointStride).coerceAtMost(ring.size - 1) - projector.project(ring.projectionTerms, sourceIndex * 4, scratch, i * 3) - if (scratch[i * 3 + 2] >= 0f) { - anyFront = true - } else { - anyBack = true - } - i++ + centerLon: Double, + viewBounds: ProjectedViewBounds +): ProjectedOcean? { + // The z0 MVT ocean feature is a water multipolygon: clockwise rings are water + // exteriors and counter-clockwise rings are land holes. The globe disc already is + // the water exterior, so projecting only the land holes avoids sending the tile's + // rectangular world edge through an orthographic horizon during every rotation. + val path = Path().apply { + fillType = PathFillType.EvenOdd + addOval(Rect(cx - r, cy - r, cx + r, cy + r)) } - if (!anyFront) return null - - val pts = ArrayList(n) - i = 0 - while (i < n) { - pts.add(DiscPt(scratch[i * 3], scratch[i * 3 + 1], scratch[i * 3 + 2] > 0.005f)) - i++ - } - val runs = buildFrontRuns(pts) - val polygon = buildFillPolygon(pts, cx, cy, r) - fillPolygonClipped( - pts = pts, - cx = cx, - cy = cy, - r = r, - color = colors.land, - clip = clip, - invertFill = anyBack && projectedFillNeedsInversion( - polygon = polygon, - ring = ring, - cx = cx, - cy = cy, - r = r, - centerLat = centerLat, - centerLon = centerLon - ), - preparedPolygon = polygon + // Normal region/country zooms remain far below Skia's coordinate precision limit, + // and the Canvas already clips them. Avoiding four software polygon-clipping passes + // here removes a large amount of per-frame allocation. + val largestPathCoordinate = maxOf( + kotlin.math.abs(cx - r), + kotlin.math.abs(cy - r), + kotlin.math.abs(cx + r), + kotlin.math.abs(cy + r) ) - return runs + val requiresClipping = largestPathCoordinate > MAX_SAFE_PATH_COORDINATE + for (polygonFeature in polygons) { + for (ring in polygonFeature.rings) { + if (ring.isMvtExterior != false) continue + if ( + minimumRingRadiusPx > 0f && + ring.sphericalBounds.angularRadius < 1.2f && + ring.sphericalBounds.angularRadius * r < minimumRingRadiusPx + ) { + continue + } + if ( + !ring.sphericalBounds.mayIntersectView( + viewBounds.centerX, + viewBounds.centerY, + viewBounds.centerZ, + viewBounds.angularRadius + ) + ) { + continue + } + val stride = if (ring.size >= 64) pointStride else 1 + val pointCount = ((ring.size - 1 + stride - 1) / stride) + 1 + if (pointCount < 3 || pointCount * 3 > scratch.size) continue + + var anyFront = false + var anyBack = false + val points = ArrayList(pointCount) + var index = 0 + while (index < pointCount) { + val sourceIndex = (index * stride).coerceAtMost(ring.size - 1) + projector.project(ring.projectionTerms, sourceIndex * 4, scratch, index * 3) + val cosC = scratch[index * 3 + 2] + if (cosC >= 0f) anyFront = true else anyBack = true + points.add( + DiscPt( + x = scratch[index * 3], + y = scratch[index * 3 + 1], + front = cosC >= 0f, + depth = cosC + ) + ) + index++ + } + + if (!anyFront) { + if (ringContainsLocation(ring, centerLat, centerLon)) { + path.addOval(Rect(cx - r, cy - r, cx + r, cy + r)) + } + continue + } + + val projectedPolygon = buildFillPolygon(points, cx, cy, r) + if (projectedPolygon.size < 3) continue + val drawablePolygon = if (requiresClipping) { + clipPolygon(projectedPolygon, clip) + } else { + projectedPolygon + } + if (drawablePolygon.size < 3) continue + if ( + anyBack && + projectedFillNeedsInversion( + polygon = projectedPolygon, + ring = ring, + cx = cx, + cy = cy, + r = r, + centerLat = centerLat, + centerLon = centerLon, + quickCheck = false + ) + ) { + path.addOval(Rect(cx - r, cy - r, cx + r, cy + r)) + } + path.moveTo(drawablePolygon[0].first, drawablePolygon[0].second) + for (pointIndex in 1 until drawablePolygon.size) { + path.lineTo( + drawablePolygon[pointIndex].first, + drawablePolygon[pointIndex].second + ) + } + path.close() + } + } + + return ProjectedOcean(path) } +private const val MAX_SAFE_PATH_COORDINATE = 30_000f + private fun DrawScope.drawGeohashGrid( state: GlobeState, cx: Float, cy: Float, r: Float, @@ -1060,7 +1605,7 @@ private fun DrawScope.drawGeohashGrid( val steps = ceil(spanLat / 1.5).toInt().coerceIn(4, 48) // Sample the cell boundary: N edge l->r, E edge t->b, S edge r->l, W edge b->t - val pts = ArrayList>(steps * 4 + 4) + val pts = ArrayList(steps * 4 + 4) fun addPt(lat: Double, lonRaw: Double) { var lon = lonRaw // keep boundary continuous across the antimeridian relative to the view @@ -1068,7 +1613,7 @@ private fun DrawScope.drawGeohashGrid( while (lon - ref > 180.0) lon -= 360.0 while (lon - ref < -180.0) lon += 360.0 val p = GlobeMath.projectRaw(lat, lon, cLat, cLon) - pts.add(Triple(p.x, p.y, p.cosC >= 0.005f)) + pts.add(DiscPt(p.x, p.y, p.cosC >= 0f, depth = p.cosC)) } for (s in 0..steps) { val t = s.toDouble() / steps @@ -1087,20 +1632,19 @@ private fun DrawScope.drawGeohashGrid( addPt(b.latMin + spanLat * t, b.lonMin) } - if (pts.none { it.third }) continue + if (pts.none { it.front }) continue - val discPts = pts.map { DiscPt(it.first, it.second, it.third) } - val runs = buildFrontRuns(discPts) + val runs = buildFrontRuns(pts) if (isSelected) { fillPolygonClipped( - discPts, cx, cy, r, colors.accent.copy(alpha = 0.20f), clip + pts, cx, cy, r, colors.accent.copy(alpha = 0.20f), clip ) strokeRuns(runs, cx, cy, r, colors.accent.copy(alpha = 0.35f), 7f, clip) strokeRuns(runs, cx, cy, r, colors.accent, 3.2f, clip) } else { fillPolygonClipped( - discPts, cx, cy, r, colors.grid.copy(alpha = 0.05f), clip + pts, cx, cy, r, colors.grid.copy(alpha = 0.05f), clip ) strokeRuns(runs, cx, cy, r, colors.grid, 1.6f, clip) } diff --git a/app/src/main/java/com/bitchat/android/ui/globe/LandData.kt b/app/src/main/java/com/bitchat/android/ui/globe/LandData.kt deleted file mode 100644 index 7348f88b..00000000 --- a/app/src/main/java/com/bitchat/android/ui/globe/LandData.kt +++ /dev/null @@ -1,164 +0,0 @@ -package com.bitchat.android.ui.globe - -import android.content.Context -import org.json.JSONObject -import kotlin.math.cos -import kotlin.math.sin - -/** - * Loads the bundled Natural Earth 110m land polygons (public domain) from assets - * and exposes them as flat rings of lat/lon pairs for vector globe rendering. - */ -object LandData { - - data class Ring( - val coords: FloatArray, - val size: Int, - /** - * Per-point sin(latitude), cos(latitude), sin(longitude), cos(longitude). - * Preparing this once removes nearly all trigonometry from animated frames. - */ - val projectionTerms: FloatArray = prepareProjectionTerms(coords, size) - ) - - data class City( - val name: String, - val lat: Float, - val lon: Float, - val rank: Int, - val capital: Boolean, - val megacity: Boolean, - val projectionTerms: FloatArray = prepareProjectionTerms( - floatArrayOf(lat, lon), - size = 1 - ) - ) - - @Volatile - private var cached: List? = null - - @Volatile - private var cachedBorders: List? = null - - @Volatile - private var cachedCities: List? = null - - /** Returns land polygon rings; each ring is a flat array of (lat, lon) pairs. */ - fun load(context: Context): List { - cached?.let { return it } - synchronized(this) { - cached?.let { return it } - val rings = mutableListOf() - val text = context.assets.open("world_land.geojson").bufferedReader().use { it.readText() } - val root = JSONObject(text) - val geometries = root.getJSONArray("geometries") - for (i in 0 until geometries.length()) { - val geom = geometries.getJSONObject(i) - when (geom.getString("type")) { - "Polygon" -> parsePolygon(geom.getJSONArray("coordinates"), rings) - "MultiPolygon" -> { - val polys = geom.getJSONArray("coordinates") - for (j in 0 until polys.length()) { - parsePolygon(polys.getJSONArray(j), rings) - } - } - } - } - cached = rings - return rings - } - } - - /** Returns country border lines (Natural Earth admin-0 boundary lines, public domain). */ - fun loadBorders(context: Context): List { - cachedBorders?.let { return it } - synchronized(this) { - cachedBorders?.let { return it } - val lines = mutableListOf() - val text = context.assets.open("world_borders.geojson").bufferedReader().use { it.readText() } - val root = JSONObject(text) - val geometries = root.getJSONArray("geometries") - for (i in 0 until geometries.length()) { - val geom = geometries.getJSONObject(i) - when (geom.getString("type")) { - "LineString" -> parseLine(geom.getJSONArray("coordinates"), lines) - "MultiLineString" -> { - val parts = geom.getJSONArray("coordinates") - for (j in 0 until parts.length()) { - parseLine(parts.getJSONArray(j), lines) - } - } - } - } - cachedBorders = lines - return lines - } - } - - /** Returns populated places (Natural Earth 50m, public domain) with name and scale rank. */ - fun loadCities(context: Context): List { - cachedCities?.let { return it } - synchronized(this) { - cachedCities?.let { return it } - val cities = mutableListOf() - val text = context.assets.open("world_cities.geojson").bufferedReader().use { it.readText() } - val root = JSONObject(text) - val arr = root.getJSONArray("cities") - for (i in 0 until arr.length()) { - val c = arr.getJSONObject(i) - cities.add( - City( - name = c.getString("n"), - lat = c.getDouble("lat").toFloat(), - lon = c.getDouble("lon").toFloat(), - rank = c.getInt("r"), - capital = c.optInt("cap", 0) == 1, - megacity = c.optInt("mega", 0) == 1 - ) - ) - } - cachedCities = cities - return cities - } - } - - private fun parseLine(lineJson: org.json.JSONArray, out: MutableList) { - val n = lineJson.length() - if (n < 2) return - val coords = FloatArray(n * 2) - for (p in 0 until n) { - val pt = lineJson.getJSONArray(p) - coords[p * 2] = pt.getDouble(1).toFloat() // lat - coords[p * 2 + 1] = pt.getDouble(0).toFloat() // lon - } - out.add(Ring(coords, n)) - } - - private fun parsePolygon(ringsJson: org.json.JSONArray, out: MutableList) { - for (r in 0 until ringsJson.length()) { - val ringJson = ringsJson.getJSONArray(r) - val n = ringJson.length() - if (n < 3) continue - val coords = FloatArray(n * 2) - for (p in 0 until n) { - val pt = ringJson.getJSONArray(p) - coords[p * 2] = pt.getDouble(1).toFloat() // lat - coords[p * 2 + 1] = pt.getDouble(0).toFloat() // lon - } - out.add(Ring(coords, n)) - } - } - - private fun prepareProjectionTerms(coords: FloatArray, size: Int): FloatArray { - val result = FloatArray(size * 4) - for (index in 0 until size) { - val latRadians = Math.toRadians(coords[index * 2].toDouble()) - val lonRadians = Math.toRadians(coords[index * 2 + 1].toDouble()) - result[index * 4] = sin(latRadians).toFloat() - result[index * 4 + 1] = cos(latRadians).toFloat() - result[index * 4 + 2] = sin(lonRadians).toFloat() - result[index * 4 + 3] = cos(lonRadians).toFloat() - } - return result - } -} diff --git a/app/src/main/java/com/bitchat/android/ui/globe/MvtDecoder.kt b/app/src/main/java/com/bitchat/android/ui/globe/MvtDecoder.kt new file mode 100644 index 00000000..ee39ebe5 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/ui/globe/MvtDecoder.kt @@ -0,0 +1,614 @@ +package com.bitchat.android.ui.globe + +import java.nio.charset.StandardCharsets +import java.util.Locale +import kotlin.math.roundToLong + +internal data class DecodedGlobeTile( + val oceanPolygons: List = emptyList(), + val borders: List = emptyList(), + val boundaryLabels: List = emptyList(), + val placeLabels: List = emptyList() +) + +/** + * Small, defensive Mapbox Vector Tile decoder for the four Shortbread layers used by the + * globe. Keeping this decoder focused avoids shipping a second rendering engine or a + * protobuf runtime simply to turn streamed coordinates back into latitude/longitude. + */ +internal class MvtDecoder( + private val preferredLanguage: String = Locale.getDefault().language +) { + fun decode(bytes: ByteArray, tile: GlobeTileKey): DecodedGlobeTile { + if (bytes.size > MAX_TILE_BYTES) { + throw MvtDecodingException("Vector tile exceeds the supported size") + } + + val result = MutableDecodedTile() + val reader = ProtoReader(bytes) + var layerCount = 0 + while (!reader.isAtEnd) { + val tag = reader.readTag() + if (tag.fieldNumber == TILE_LAYER_FIELD && tag.wireType == WIRE_LENGTH_DELIMITED) { + layerCount++ + if (layerCount > MAX_LAYERS) { + throw MvtDecodingException("Vector tile contains too many layers") + } + decodeLayer(reader.readBytes(), tile, result) + } else { + reader.skip(tag.wireType) + } + } + return result.freeze() + } + + private fun decodeLayer( + bytes: ByteArray, + tile: GlobeTileKey, + output: MutableDecodedTile + ) { + // Shortbread contains many rendering layers (roads, buildings, POIs, and more). + // Find the layer name without copying its features, then completely skip layers + // this globe never uses. + val layerName = readLayerName(bytes) ?: return + if (layerName !in SUPPORTED_LAYERS) return + + val reader = ProtoReader(bytes) + var name = layerName + var extent = DEFAULT_EXTENT + val keys = ArrayList() + val values = ArrayList() + val rawFeatures = ArrayList() + + while (!reader.isAtEnd) { + val tag = reader.readTag() + when { + tag.fieldNumber == LAYER_NAME_FIELD && + tag.wireType == WIRE_LENGTH_DELIMITED -> { + name = reader.readString() + } + tag.fieldNumber == LAYER_FEATURE_FIELD && + tag.wireType == WIRE_LENGTH_DELIMITED -> { + if (rawFeatures.size >= MAX_FEATURES_PER_LAYER) { + throw MvtDecodingException("Vector tile layer contains too many features") + } + rawFeatures.add(reader.readBytes()) + } + tag.fieldNumber == LAYER_KEY_FIELD && + tag.wireType == WIRE_LENGTH_DELIMITED -> { + if (keys.size >= MAX_DICTIONARY_ENTRIES) { + throw MvtDecodingException("Vector tile key dictionary is too large") + } + keys.add(reader.readString()) + } + tag.fieldNumber == LAYER_VALUE_FIELD && + tag.wireType == WIRE_LENGTH_DELIMITED -> { + if (values.size >= MAX_DICTIONARY_ENTRIES) { + throw MvtDecodingException("Vector tile value dictionary is too large") + } + values.add(decodeValue(reader.readBytes())) + } + tag.fieldNumber == LAYER_EXTENT_FIELD && tag.wireType == WIRE_VARINT -> { + extent = reader.readVarint().toInt() + if (extent !in 1..MAX_EXTENT) { + throw MvtDecodingException("Unsupported vector tile extent") + } + } + else -> reader.skip(tag.wireType) + } + } + + rawFeatures.forEach { featureBytes -> + decodeFeature(name, featureBytes, keys, values, extent, tile, output) + } + } + + private fun readLayerName(bytes: ByteArray): String? { + val reader = ProtoReader(bytes) + while (!reader.isAtEnd) { + val tag = reader.readTag() + if ( + tag.fieldNumber == LAYER_NAME_FIELD && + tag.wireType == WIRE_LENGTH_DELIMITED + ) { + return reader.readString() + } + reader.skip(tag.wireType) + } + return null + } + + private fun decodeFeature( + layerName: String, + bytes: ByteArray, + keys: List, + values: List, + extent: Int, + tile: GlobeTileKey, + output: MutableDecodedTile + ) { + val reader = ProtoReader(bytes) + var geometryType = GEOMETRY_UNKNOWN + var rawTags = IntArray(0) + var geometry = IntArray(0) + + while (!reader.isAtEnd) { + val tag = reader.readTag() + when { + tag.fieldNumber == FEATURE_TAGS_FIELD -> { + rawTags = reader.readPackedUInt32(tag.wireType, MAX_TAG_VALUES) + } + tag.fieldNumber == FEATURE_TYPE_FIELD && tag.wireType == WIRE_VARINT -> { + geometryType = reader.readVarint().toInt() + } + tag.fieldNumber == FEATURE_GEOMETRY_FIELD -> { + geometry = reader.readPackedUInt32(tag.wireType, MAX_GEOMETRY_VALUES) + } + else -> reader.skip(tag.wireType) + } + } + if (geometry.isEmpty()) return + + val properties = decodeProperties(rawTags, keys, values) + when (layerName) { + OCEAN_LAYER -> { + if (geometryType != GEOMETRY_POLYGON) return + val paths = decodeGeometry(geometry, geometryType) + val rings = paths.mapNotNull { path -> + path.toGeoRing( + tile = tile, + extent = extent, + minimumPoints = 3, + isMvtExterior = path.signedAreaTwice() > 0L + ) + } + if (rings.isNotEmpty()) output.oceanPolygons.add(OceanPolygon(rings)) + } + BOUNDARIES_LAYER -> { + if (geometryType != GEOMETRY_LINESTRING) return + val maritime = properties.booleanValue("maritime") + val disputed = properties.booleanValue("disputed") + val adminLevel = properties.intValue("admin_level") + decodeGeometry(geometry, geometryType).forEach { path -> + val ring = path.toGeoRing(tile, extent, minimumPoints = 2) ?: return@forEach + output.borders.add( + BorderLine( + ring = ring, + maritime = maritime, + disputed = disputed, + adminLevel = adminLevel + ) + ) + } + } + BOUNDARY_LABELS_LAYER -> { + if (geometryType != GEOMETRY_POINT) return + val name = properties.localizedName() ?: return + val adminLevel = properties.intValue("admin_level") + val kind = if (adminLevel == 2) MapLabelKind.COUNTRY else MapLabelKind.STATE + val importance = properties.longValue("way_area") ?: 0L + decodeGeometry(geometry, geometryType).forEach { path -> + val point = path.firstOrNull() ?: return@forEach + output.boundaryLabels.add( + point.toMapLabel(tile, extent, name, kind, importance) + ) + } + } + PLACE_LABELS_LAYER -> { + if (geometryType != GEOMETRY_POINT) return + val name = properties.localizedName() ?: return + val kind = mapPlaceKind(properties.stringValue("kind")) + val population = properties.longValue("population") ?: 0L + decodeGeometry(geometry, geometryType).forEach { path -> + val point = path.firstOrNull() ?: return@forEach + output.placeLabels.add( + point.toMapLabel(tile, extent, name, kind, population) + ) + } + } + } + } + + private fun decodeProperties( + rawTags: IntArray, + keys: List, + values: List + ): Map { + if (rawTags.size % 2 != 0) { + throw MvtDecodingException("Vector tile feature has malformed tags") + } + return buildMap(rawTags.size / 2) { + var index = 0 + while (index < rawTags.size) { + val keyIndex = rawTags[index] + val valueIndex = rawTags[index + 1] + if (keyIndex !in keys.indices || valueIndex !in values.indices) { + throw MvtDecodingException("Vector tile feature references an invalid tag") + } + put(keys[keyIndex], values[valueIndex]) + index += 2 + } + } + } + + private fun decodeValue(bytes: ByteArray): Any? { + val reader = ProtoReader(bytes) + var value: Any? = null + while (!reader.isAtEnd) { + val tag = reader.readTag() + value = when { + tag.fieldNumber == VALUE_STRING_FIELD && + tag.wireType == WIRE_LENGTH_DELIMITED -> reader.readString() + tag.fieldNumber == VALUE_FLOAT_FIELD && + tag.wireType == WIRE_FIXED32 -> Float.fromBits(reader.readFixed32()) + tag.fieldNumber == VALUE_DOUBLE_FIELD && + tag.wireType == WIRE_FIXED64 -> Double.fromBits(reader.readFixed64()) + tag.fieldNumber == VALUE_INT_FIELD && + tag.wireType == WIRE_VARINT -> reader.readVarint() + tag.fieldNumber == VALUE_UINT_FIELD && + tag.wireType == WIRE_VARINT -> reader.readVarint() + tag.fieldNumber == VALUE_SINT_FIELD && + tag.wireType == WIRE_VARINT -> decodeZigZag64(reader.readVarint()) + tag.fieldNumber == VALUE_BOOL_FIELD && + tag.wireType == WIRE_VARINT -> reader.readVarint() != 0L + else -> { + reader.skip(tag.wireType) + value + } + } + } + return value + } + + private fun decodeGeometry(encoded: IntArray, geometryType: Int): List> { + val paths = ArrayList>() + var currentPath: MutableList? = null + var cursorX = 0 + var cursorY = 0 + var index = 0 + + while (index < encoded.size) { + val commandInteger = encoded[index++] + val command = commandInteger and COMMAND_ID_MASK + val count = commandInteger ushr COMMAND_COUNT_SHIFT + if (count <= 0 || count > MAX_COMMAND_COUNT) { + throw MvtDecodingException("Vector tile geometry has an invalid command count") + } + when (command) { + COMMAND_MOVE_TO, COMMAND_LINE_TO -> { + repeat(count) { + if (index + 1 >= encoded.size) { + throw MvtDecodingException("Vector tile geometry is truncated") + } + cursorX += decodeZigZag32(encoded[index++]) + cursorY += decodeZigZag32(encoded[index++]) + val targetPath = if ( + geometryType == GEOMETRY_POINT || + command == COMMAND_MOVE_TO || + currentPath == null + ) { + ArrayList().also { newPath -> + currentPath = newPath + paths.add(newPath) + } + } else { + requireNotNull(currentPath) + } + targetPath.add(TilePoint(cursorX, cursorY)) + } + } + COMMAND_CLOSE_PATH -> { + if (geometryType != GEOMETRY_POLYGON || count != 1 || currentPath == null) { + throw MvtDecodingException("Vector tile geometry has an invalid close command") + } + } + else -> throw MvtDecodingException("Vector tile geometry uses an unknown command") + } + } + return paths + } + + private fun List.toGeoRing( + tile: GlobeTileKey, + extent: Int, + minimumPoints: Int, + isMvtExterior: Boolean? = null + ): GeoRing? { + if (size < minimumPoints) return null + val coords = FloatArray(size * 2) + forEachIndexed { index, point -> + coords[index * 2] = GlobeTileSelector.tilePointToLatitude( + tile.y, point.y, extent, tile.zoom + ).toFloat() + coords[index * 2 + 1] = GlobeTileSelector.tilePointToLongitude( + tile.x, point.x, extent, tile.zoom + ).toFloat() + } + return GeoRing( + coords = coords, + size = size, + isMvtExterior = isMvtExterior + ) + } + + private fun List.signedAreaTwice(): Long { + if (size < 3) return 0L + var area = 0L + var previous = last() + for (point in this) { + area += previous.x.toLong() * point.y - point.x.toLong() * previous.y + previous = point + } + return area + } + + private fun TilePoint.toMapLabel( + tile: GlobeTileKey, + extent: Int, + name: String, + kind: MapLabelKind, + importance: Long + ): MapLabel { + return MapLabel( + name = name, + lat = GlobeTileSelector.tilePointToLatitude( + tile.y, y, extent, tile.zoom + ).toFloat(), + lon = GlobeTileSelector.tilePointToLongitude( + tile.x, x, extent, tile.zoom + ).toFloat(), + kind = kind, + importance = importance + ) + } + + private fun Map.localizedName(): String? { + val preferred = preferredLanguage + .takeIf { it.matches(LANGUAGE_CODE) } + ?.let { stringValue("name_$it") } + return sequenceOf(preferred, stringValue("name"), stringValue("name_en")) + .filterNotNull() + .map(String::trim) + .firstOrNull { it.isNotEmpty() } + } + + private fun Map.stringValue(key: String): String? = get(key) as? String + + private fun Map.intValue(key: String): Int? { + return when (val value = get(key)) { + is Number -> value.toInt() + is String -> value.toIntOrNull() + else -> null + } + } + + private fun Map.longValue(key: String): Long? { + return when (val value = get(key)) { + is Number -> value.toDouble().roundToLong() + is String -> value.toDoubleOrNull()?.roundToLong() + else -> null + } + } + + private fun Map.booleanValue(key: String): Boolean { + return when (val value = get(key)) { + is Boolean -> value + is Number -> value.toInt() != 0 + is String -> value == "1" || value.equals("true", ignoreCase = true) + else -> false + } + } + + private fun mapPlaceKind(kind: String?): MapLabelKind { + return when (kind) { + "capital", "state_capital" -> MapLabelKind.CAPITAL + "city" -> MapLabelKind.CITY + "town" -> MapLabelKind.TOWN + "village", "hamlet" -> MapLabelKind.VILLAGE + else -> MapLabelKind.OTHER + } + } + + private data class TilePoint(val x: Int, val y: Int) + + private class MutableDecodedTile { + val oceanPolygons = ArrayList() + val borders = ArrayList() + val boundaryLabels = ArrayList() + val placeLabels = ArrayList() + + fun freeze() = DecodedGlobeTile( + oceanPolygons = oceanPolygons, + borders = borders, + boundaryLabels = boundaryLabels, + placeLabels = placeLabels + ) + } + + companion object { + internal const val MAX_TILE_BYTES = 2 * 1024 * 1024 + private const val MAX_LAYERS = 64 + private const val MAX_FEATURES_PER_LAYER = 100_000 + private const val MAX_DICTIONARY_ENTRIES = 100_000 + private const val MAX_TAG_VALUES = 100_000 + private const val MAX_GEOMETRY_VALUES = 1_000_000 + private const val MAX_COMMAND_COUNT = 1_000_000 + private const val MAX_EXTENT = 65_536 + private const val DEFAULT_EXTENT = 4096 + + private const val TILE_LAYER_FIELD = 3 + private const val LAYER_NAME_FIELD = 1 + private const val LAYER_FEATURE_FIELD = 2 + private const val LAYER_KEY_FIELD = 3 + private const val LAYER_VALUE_FIELD = 4 + private const val LAYER_EXTENT_FIELD = 5 + private const val FEATURE_TAGS_FIELD = 2 + private const val FEATURE_TYPE_FIELD = 3 + private const val FEATURE_GEOMETRY_FIELD = 4 + private const val VALUE_STRING_FIELD = 1 + private const val VALUE_FLOAT_FIELD = 2 + private const val VALUE_DOUBLE_FIELD = 3 + private const val VALUE_INT_FIELD = 4 + private const val VALUE_UINT_FIELD = 5 + private const val VALUE_SINT_FIELD = 6 + private const val VALUE_BOOL_FIELD = 7 + + private const val OCEAN_LAYER = "ocean" + private const val BOUNDARIES_LAYER = "boundaries" + private const val BOUNDARY_LABELS_LAYER = "boundary_labels" + private const val PLACE_LABELS_LAYER = "place_labels" + private val SUPPORTED_LAYERS = setOf( + OCEAN_LAYER, + BOUNDARIES_LAYER, + BOUNDARY_LABELS_LAYER, + PLACE_LABELS_LAYER + ) + + private const val GEOMETRY_UNKNOWN = 0 + private const val GEOMETRY_POINT = 1 + private const val GEOMETRY_LINESTRING = 2 + private const val GEOMETRY_POLYGON = 3 + private const val COMMAND_MOVE_TO = 1 + private const val COMMAND_LINE_TO = 2 + private const val COMMAND_CLOSE_PATH = 7 + private const val COMMAND_ID_MASK = 0x7 + private const val COMMAND_COUNT_SHIFT = 3 + private val LANGUAGE_CODE = Regex("[a-z]{2,3}") + + private fun decodeZigZag32(value: Int): Int = (value ushr 1) xor -(value and 1) + private fun decodeZigZag64(value: Long): Long = (value ushr 1) xor -(value and 1L) + } +} + +internal class MvtDecodingException(message: String) : Exception(message) + +private data class ProtoTag(val fieldNumber: Int, val wireType: Int) + +private class ProtoReader(private val bytes: ByteArray) { + private var position = 0 + val isAtEnd: Boolean get() = position >= bytes.size + + fun readTag(): ProtoTag { + val rawTag = readVarint() + val fieldNumber = (rawTag ushr 3).toInt() + val wireType = (rawTag and 0x7).toInt() + if (fieldNumber <= 0) throw MvtDecodingException("Invalid protobuf field") + return ProtoTag(fieldNumber, wireType) + } + + fun readVarint(): Long { + var result = 0L + var shift = 0 + while (shift < 64) { + if (position >= bytes.size) throw MvtDecodingException("Truncated protobuf value") + val value = bytes[position++].toInt() and 0xff + result = result or ((value and 0x7f).toLong() shl shift) + if ((value and 0x80) == 0) return result + shift += 7 + } + throw MvtDecodingException("Malformed protobuf value") + } + + fun readFixed32(): Int { + requireAvailable(4) + val result = + (bytes[position].toInt() and 0xff) or + ((bytes[position + 1].toInt() and 0xff) shl 8) or + ((bytes[position + 2].toInt() and 0xff) shl 16) or + ((bytes[position + 3].toInt() and 0xff) shl 24) + position += 4 + return result + } + + fun readFixed64(): Long { + requireAvailable(8) + var result = 0L + for (offset in 0 until 8) { + result = result or ((bytes[position + offset].toLong() and 0xffL) shl (offset * 8)) + } + position += 8 + return result + } + + fun readBytes(): ByteArray { + val length = readLength() + requireAvailable(length) + return bytes.copyOfRange(position, position + length).also { position += length } + } + + fun readString(): String { + val value = readBytes() + if (value.size > MAX_STRING_BYTES) { + throw MvtDecodingException("Vector tile string is too long") + } + return String(value, StandardCharsets.UTF_8) + } + + fun readPackedUInt32(wireType: Int, maximumValues: Int): IntArray { + return when (wireType) { + WIRE_VARINT -> intArrayOf(readVarint().checkedUInt32()) + WIRE_LENGTH_DELIMITED -> { + val packedReader = ProtoReader(readBytes()) + val result = ArrayList() + while (!packedReader.isAtEnd) { + if (result.size >= maximumValues) { + throw MvtDecodingException("Packed protobuf field is too large") + } + result.add(packedReader.readVarint().checkedUInt32()) + } + result.toIntArray() + } + else -> throw MvtDecodingException("Unexpected protobuf wire type") + } + } + + fun skip(wireType: Int) { + when (wireType) { + WIRE_VARINT -> readVarint() + WIRE_FIXED64 -> { + requireAvailable(8) + position += 8 + } + WIRE_LENGTH_DELIMITED -> { + val length = readLength() + requireAvailable(length) + position += length + } + WIRE_FIXED32 -> { + requireAvailable(4) + position += 4 + } + else -> throw MvtDecodingException("Unsupported protobuf wire type") + } + } + + private fun readLength(): Int { + val length = readVarint() + if (length < 0L || length > Int.MAX_VALUE) { + throw MvtDecodingException("Invalid protobuf length") + } + return length.toInt() + } + + private fun requireAvailable(count: Int) { + if (count < 0 || position > bytes.size - count) { + throw MvtDecodingException("Truncated protobuf field") + } + } + + private fun Long.checkedUInt32(): Int { + if (this < 0L || this > Int.MAX_VALUE) { + throw MvtDecodingException("Protobuf integer exceeds uint32") + } + return toInt() + } + + companion object { + private const val MAX_STRING_BYTES = 16 * 1024 + } +} + +private const val WIRE_VARINT = 0 +private const val WIRE_FIXED64 = 1 +private const val WIRE_LENGTH_DELIMITED = 2 +private const val WIRE_FIXED32 = 5 diff --git a/app/src/main/java/com/bitchat/android/ui/globe/StreamedGlobeRepository.kt b/app/src/main/java/com/bitchat/android/ui/globe/StreamedGlobeRepository.kt new file mode 100644 index 00000000..ec127d30 --- /dev/null +++ b/app/src/main/java/com/bitchat/android/ui/globe/StreamedGlobeRepository.kt @@ -0,0 +1,264 @@ +package com.bitchat.android.ui.globe + +import android.content.Context +import com.bitchat.android.BuildConfig +import com.bitchat.android.net.OkHttpProvider +import java.io.ByteArrayOutputStream +import java.io.Closeable +import java.io.IOException +import java.util.LinkedHashMap +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlinx.coroutines.withContext +import okhttp3.Cache +import okhttp3.OkHttpClient +import okhttp3.Request + +/** + * Streams only the visible Shortbread vector tiles from OpenStreetMap. + * + * The disk cache follows server cache headers (with a seven-day fallback), while the small + * decoded LRU prevents repeated protobuf work during globe rotation. No map dataset is + * bundled with or permanently stored by the app. + */ +internal class StreamedGlobeRepository( + context: Context, + private val tileUrl: (GlobeTileKey) -> String = { tile -> + "$DEFAULT_TILE_ROOT/${tile.zoom}/${tile.x}/${tile.y}.mvt" + }, + private val decoder: MvtDecoder = MvtDecoder() +) : Closeable { + private val tileCache = Cache( + directory = context.cacheDir.resolve(CACHE_DIRECTORY), + maxSize = DISK_CACHE_BYTES + ) + private val fetchSemaphore = Semaphore(MAX_CONCURRENT_FETCHES) + private val decodeSemaphore = Semaphore(MAX_CONCURRENT_DECODES) + private val decodedTiles = object : + LinkedHashMap( + MEMORY_CACHE_ENTRIES, + 0.75f, + true + ) { + override fun removeEldestEntry( + eldest: MutableMap.MutableEntry? + ): Boolean = size > MEMORY_CACHE_ENTRIES + } + + suspend fun load( + request: GlobeTileRequest, + onProgress: suspend (GlobeMapLoadResult) -> Unit = {} + ): GlobeMapLoadResult { + val priorityTiles = buildList { + add(GLOBAL_OCEAN_TILE) + request.priorityTiles + .asSequence() + .filterNot { it == GLOBAL_OCEAN_TILE } + .forEach(::add) + } + val remainingTiles = request.detailTiles + .asSequence() + .filterNot { it == GLOBAL_OCEAN_TILE || it in request.priorityTiles } + .sortedWith(compareBy(GlobeTileKey::zoom, GlobeTileKey::y, GlobeTileKey::x)) + .toList() + val client = currentTileClient() + val outcomes = ArrayList(priorityTiles.size + remainingTiles.size) + outcomes += loadTiles(client, priorityTiles) + + if (remainingTiles.isNotEmpty()) { + onProgress(assembleResult(request, outcomes)) + outcomes += loadTiles(client, remainingTiles) + } + return assembleResult(request, outcomes) + } + + private suspend fun loadTiles( + client: OkHttpClient, + tiles: List + ): List = coroutineScope { + tiles.map { tile -> + async { + try { + TileOutcome(tile, fetchTile(client, tile), null) + } catch (cancellation: CancellationException) { + throw cancellation + } catch (error: Exception) { + TileOutcome(tile, null, error) + } + } + }.awaitAll() + } + + private suspend fun assembleResult( + request: GlobeTileRequest, + outcomes: List + ): GlobeMapLoadResult = withContext(Dispatchers.Default) { + val successful = outcomes.filter { it.decoded != null } + if (successful.isEmpty()) { + throw IOException("OpenStreetMap vector tiles are unavailable") + } + + val globalOcean = successful + .firstOrNull { it.tile == GLOBAL_OCEAN_TILE } + ?.decoded + ?.oceanPolygons + .orEmpty() + if (globalOcean.isEmpty()) { + throw IOException("The global OpenStreetMap ocean layer is unavailable") + } + val detailTiles = successful.filter { it.tile != GLOBAL_OCEAN_TILE } + val boundaryLabels = deduplicateLabels( + detailTiles.flatMap { it.decoded?.boundaryLabels.orEmpty() } + ) + val placeLabels = deduplicateLabels( + detailTiles.flatMap { it.decoded?.placeLabels.orEmpty() } + ) + + GlobeMapLoadResult( + data = GlobeMapData( + oceanPolygons = globalOcean, + borders = detailTiles.flatMap { it.decoded?.borders.orEmpty() }, + boundaryLabels = boundaryLabels, + placeLabels = placeLabels, + detailZoom = request.detailZoom + ), + requestedTileCount = outcomes.size, + failedTileCount = outcomes.count { it.error != null } + ) + } + + private fun currentTileClient(): OkHttpClient { + return OkHttpProvider.httpClient() + .newBuilder() + .cache(tileCache) + .addNetworkInterceptor { chain -> + val response = chain.proceed(chain.request()) + if (response.header("Cache-Control") != null) { + response + } else { + response.newBuilder() + .header("Cache-Control", "public, max-age=$FALLBACK_CACHE_SECONDS") + .build() + } + } + .build() + } + + private suspend fun fetchTile( + client: OkHttpClient, + tile: GlobeTileKey + ): DecodedGlobeTile { + synchronized(decodedTiles) { + decodedTiles[tile]?.let { return it } + } + + val bytes = fetchSemaphore.withPermit { + runInterruptible(Dispatchers.IO) { + val request = Request.Builder() + .url(tileUrl(tile)) + .header("Accept", VECTOR_TILE_MEDIA_TYPE) + .header("User-Agent", USER_AGENT) + .get() + .build() + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) { + throw IOException( + "OpenStreetMap tile request failed (${response.code})" + ) + } + response.body.readBytesWithLimit(MvtDecoder.MAX_TILE_BYTES) + } + } + } + val decoded = decodeSemaphore.withPermit { + withContext(Dispatchers.Default) { + decoder.decode(bytes, tile) + } + } + synchronized(decodedTiles) { + decodedTiles[tile] = decoded + } + return decoded + } + + override fun close() { + tileCache.close() + synchronized(decodedTiles) { + decodedTiles.clear() + } + } + + private fun okhttp3.ResponseBody.readBytesWithLimit(maximumBytes: Int): ByteArray { + contentLength().takeIf { it >= 0L }?.let { length -> + if (length > maximumBytes) { + throw IOException("OpenStreetMap tile response is too large") + } + } + byteStream().use { input -> + val output = ByteArrayOutputStream() + val buffer = ByteArray(8 * 1024) + var total = 0 + while (true) { + val count = input.read(buffer) + if (count < 0) break + total += count + if (total > maximumBytes) { + throw IOException("OpenStreetMap tile response is too large") + } + output.write(buffer, 0, count) + } + return output.toByteArray() + } + } + + private fun deduplicateLabels(labels: List): List { + return labels + .sortedWith( + compareBy { it.rank } + .thenByDescending { it.importance } + .thenBy { it.name } + ) + .distinctBy { label -> + LabelIdentity( + name = label.name, + roundedLatitude = (label.lat * LABEL_DEDUPLICATION_SCALE).toInt(), + roundedLongitude = (label.lon * LABEL_DEDUPLICATION_SCALE).toInt() + ) + } + } + + private data class TileOutcome( + val tile: GlobeTileKey, + val decoded: DecodedGlobeTile?, + val error: Exception? + ) + + private data class LabelIdentity( + val name: String, + val roundedLatitude: Int, + val roundedLongitude: Int + ) + + companion object { + private const val DEFAULT_TILE_ROOT = + "https://vector.openstreetmap.org/shortbread_v1" + private const val CACHE_DIRECTORY = "openstreetmap_globe_tiles" + private const val DISK_CACHE_BYTES = 32L * 1024L * 1024L + private const val MEMORY_CACHE_ENTRIES = 64 + private const val MAX_CONCURRENT_FETCHES = 4 + private const val MAX_CONCURRENT_DECODES = 2 + private const val FALLBACK_CACHE_SECONDS = 7L * 24L * 60L * 60L + private const val LABEL_DEDUPLICATION_SCALE = 100 + private const val VECTOR_TILE_MEDIA_TYPE = "application/vnd.mapbox-vector-tile" + private val GLOBAL_OCEAN_TILE = GlobeTileKey(0, 0, 0) + private val USER_AGENT = + "Bitchat-Android/${BuildConfig.VERSION_NAME} " + + "(+https://github.com/permissionlesstech/bitchat-android)" + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f705c5ad..14ecf409 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -540,6 +540,9 @@ Nobody around… (you) Drag to spin · Pinch to zoom · Tap to focus + Loading OpenStreetMap data… + Map data is temporarily unavailable + © OpenStreetMap contributors Select Type a message… @%1$s diff --git a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeGeometryTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeGeometryTest.kt index 122e1235..4a2bf7e2 100644 --- a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeGeometryTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeGeometryTest.kt @@ -46,7 +46,7 @@ class GlobeGeometryTest { @Test fun ringContainsLocation_distinguishesInsideAndOutsideCoordinates() { - val ring = LandData.Ring( + val ring = GeoRing( coords = floatArrayOf( -10f, -10f, -10f, 10f, @@ -59,4 +59,68 @@ class GlobeGeometryTest { assertTrue(ringContainsLocation(ring, latitude = 0.0, longitude = 0.0)) assertFalse(ringContainsLocation(ring, latitude = 20.0, longitude = 0.0)) } + + @Test + fun compactRingBoundsCullOnlyDistantViews() { + val ring = GeoRing( + coords = floatArrayOf( + -2f, -2f, + -2f, 2f, + 2f, 2f, + 2f, -2f + ), + size = 4 + ) + + assertTrue( + ring.sphericalBounds.mayIntersectView( + viewCenterX = 1f, + viewCenterY = 0f, + viewCenterZ = 0f, + viewAngularRadius = 0.1f + ) + ) + assertFalse( + ring.sphericalBounds.mayIntersectView( + viewCenterX = -1f, + viewCenterY = 0f, + viewCenterZ = 0f, + viewAngularRadius = 0.1f + ) + ) + } + + @Test + fun limbPoint_intersectsTheTrueProjectionHorizon() { + val point = limbPoint( + DiscPt(x = 0.2f, y = 0f, front = false, depth = -0.8f), + DiscPt(x = 0.6f, y = 0f, front = true, depth = 0.8f) + ) + + assertEquals(1f, point.first, 0.0001f) + assertEquals(0f, point.second, 0.0001f) + } + + @Test + fun fillPolygon_replacesBacksideVerticesWithOneSmoothLimbArc() { + val polygon = buildFillPolygon( + pts = listOf( + DiscPt(-0.6f, -0.2f, front = true, depth = 0.5f), + DiscPt(-0.2f, 0.1f, front = false, depth = -0.5f), + DiscPt(0.2f, -0.1f, front = false, depth = -0.5f), + DiscPt(0.6f, -0.2f, front = true, depth = 0.5f), + DiscPt(0f, -0.6f, front = true, depth = 0.5f) + ), + cx = 0f, + cy = 0f, + r = 1f + ) + + assertTrue(polygon.size >= 5) + assertTrue(polygon.all { (x, y) -> x * x + y * y <= 1.0001f }) + assertFalse(polygon.any { (x, y) -> + kotlin.math.abs(x + 0.2f) < 0.0001f && + kotlin.math.abs(y - 0.1f) < 0.0001f + }) + } } diff --git a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeInteractionPolicyTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeInteractionPolicyTest.kt index c4e59cd3..9ec4a10e 100644 --- a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeInteractionPolicyTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeInteractionPolicyTest.kt @@ -12,54 +12,78 @@ class GlobeInteractionPolicyTest { fun fullDetail_preservesAllGlobeFeatures() { val detail = globeFrameDetail(GlobeMotionDetail.FULL) + assertTrue(detail.showGraticule) assertEquals(1, detail.landPointStride) + assertEquals(0f, detail.minimumLandRingRadiusPx) assertTrue(detail.showBorders) assertNull(detail.cityMaxRank) assertTrue(detail.showCityLabels) + assertTrue(detail.showStateLabels) + assertNull(detail.maximumBoundaryLabels) assertTrue(detail.showGeohashGrid) assertTrue(detail.showNeighborCells) } @Test - fun balancedDetail_preservesOrientationAndSelection() { - val detail = globeFrameDetail(GlobeMotionDetail.BALANCED) - - assertEquals(2, detail.landPointStride) - assertTrue(detail.showBorders) - assertEquals(1, detail.cityMaxRank) - assertFalse(detail.showCityLabels) - assertTrue(detail.showGeohashGrid) - assertFalse(detail.showNeighborCells) - } - - @Test - fun fastDetail_usesMinimumMovingFrameWork() { + fun fastDetail_preservesTheCompleteVisualDesignDuringMovement() { val detail = globeFrameDetail(GlobeMotionDetail.FAST) - assertEquals(2, detail.landPointStride) - assertFalse(detail.showBorders) - assertEquals(-1, detail.cityMaxRank) - assertFalse(detail.showCityLabels) - assertFalse(detail.showGeohashGrid) + assertTrue(detail.showGraticule) + assertEquals(1, detail.landPointStride) + assertEquals(0f, detail.minimumLandRingRadiusPx) + assertTrue(detail.showBorders) + assertNull(detail.cityMaxRank) + assertTrue(detail.showCityLabels) + assertTrue(detail.showStateLabels) + assertNull(detail.maximumBoundaryLabels) + assertTrue(detail.showGeohashGrid) + assertTrue(detail.showNeighborCells) + assertEquals(globeFrameDetail(GlobeMotionDetail.FULL), detail) } @Test - fun adaptiveDetail_degradesOnSlowFramesAndRecoversWithHysteresis() { + fun labelTransition_keepsCurrentOrderThenFadesRemovedPlaces() { + val retainedBefore = city("Retained", 12f, 34f) + val removed = city("Removed", 20f, 40f) + val retainedAfter = city("Retained", 12f, 34f) + val added = city("Added", 30f, 50f) + + val transition = buildMapLabelTransition( + previous = listOf(retainedBefore, removed), + current = listOf(retainedAfter, added) + ) + + assertTrue(transition.hasChanges) assertEquals( - GlobeMotionDetail.BALANCED, - nextGlobeMotionDetail(GlobeMotionDetail.FULL, averageFrameMillis = 24f) + listOf("Retained", "Added", "Removed"), + transition.labels.map { it.label.name } ) assertEquals( - GlobeMotionDetail.FAST, - nextGlobeMotionDetail(GlobeMotionDetail.BALANCED, averageFrameMillis = 32f) - ) - assertEquals( - GlobeMotionDetail.BALANCED, - nextGlobeMotionDetail(GlobeMotionDetail.FAST, averageFrameMillis = 17f) - ) - assertEquals( - GlobeMotionDetail.FULL, - nextGlobeMotionDetail(GlobeMotionDetail.BALANCED, averageFrameMillis = 17f) + listOf( + MapLabelTransitionPhase.STABLE, + MapLabelTransitionPhase.ENTERING, + MapLabelTransitionPhase.EXITING + ), + transition.labels.map { it.phase } ) } + + @Test + fun labelTransition_matchesRedecodedLabelsByContent() { + val transition = buildMapLabelTransition( + previous = listOf(city("Stable", 12f, 34f)), + current = listOf(city("Stable", 12f, 34f)) + ) + + assertFalse(transition.hasChanges) + assertEquals(MapLabelTransitionPhase.STABLE, transition.labels.single().phase) + } + + private fun city(name: String, lat: Float, lon: Float) = MapLabel( + name = name, + lat = lat, + lon = lon, + kind = MapLabelKind.CITY, + importance = 100_000L + ) } diff --git a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeTileSelectorTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeTileSelectorTest.kt new file mode 100644 index 00000000..7471e77b --- /dev/null +++ b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeTileSelectorTest.kt @@ -0,0 +1,95 @@ +package com.bitchat.android.ui.globe + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class GlobeTileSelectorTest { + @Test + fun wholeGlobeRequestsAllZoomTwoTiles() { + val request = GlobeTileSelector.select( + GlobeViewport( + centerLat = 20.0, + centerLon = 170.0, + globeRadiusPx = 400f, + widthPx = 1080, + heightPx = 1920 + ) + ) + + requireNotNull(request) + assertEquals(2, request.detailZoom) + assertEquals(16, request.detailTiles.size) + assertEquals((0..3).toSet(), request.detailTiles.map { it.x }.toSet()) + assertEquals((0..3).toSet(), request.detailTiles.map { it.y }.toSet()) + assertTrue(request.priorityTiles.isNotEmpty()) + assertTrue(request.priorityTiles.all { it in request.detailTiles }) + } + + @Test + fun nearFittedGlobeUsesOverviewDetailRegardlessOfDisplayDensity() { + val request = GlobeTileSelector.select( + GlobeViewport( + centerLat = 20.0, + centerLon = 0.0, + globeRadiusPx = 1_000f, + widthPx = 1_344, + heightPx = 2_992 + ) + ) + + requireNotNull(request) + assertEquals(2, request.detailZoom) + assertTrue(request.detailTiles.size <= GlobeTileSelector.MAX_VISIBLE_TILES) + } + + @Test + fun zoomedGlobeKeepsVisibleRequestsBoundedAcrossAntimeridian() { + val request = GlobeTileSelector.select( + GlobeViewport( + centerLat = 35.0, + centerLon = 179.8, + globeRadiusPx = 18_000f, + widthPx = 1080, + heightPx = 1920 + ) + ) + + requireNotNull(request) + assertTrue(request.detailZoom in 2..GlobeTileSelector.MAX_TILE_ZOOM) + assertTrue(request.detailTiles.size <= GlobeTileSelector.MAX_VISIBLE_TILES) + val dimension = 1 shl request.detailZoom + assertTrue(request.detailTiles.all { it.x in 0 until dimension }) + assertTrue(request.detailTiles.all { it.y in 0 until dimension }) + assertTrue(request.detailTiles.any { it.x == 0 }) + assertTrue(request.detailTiles.any { it.x == dimension - 1 }) + } + + @Test + fun tileCoordinateRoundTripIsAccurate() { + val zoom = 8 + val extent = 4096 + val tileX = GlobeTileSelector.longitudeToTileX(13.405, zoom) + val tileY = GlobeTileSelector.latitudeToTileY(52.52, zoom) + val dimension = 1 shl zoom + val worldX = (13.405 + 180.0) / 360.0 * dimension + val worldY = ( + 1.0 - kotlin.math.ln( + kotlin.math.tan(Math.toRadians(52.52)) + + 1.0 / kotlin.math.cos(Math.toRadians(52.52)) + ) / Math.PI + ) / 2.0 * dimension + val localX = ((worldX - tileX) * extent).toInt() + val localY = ((worldY - tileY) * extent).toInt() + + val longitude = GlobeTileSelector.tilePointToLongitude( + tileX, localX, extent, zoom + ) + val latitude = GlobeTileSelector.tilePointToLatitude( + tileY, localY, extent, zoom + ) + + assertEquals(13.405, longitude, 0.001) + assertEquals(52.52, latitude, 0.001) + } +} diff --git a/app/src/test/kotlin/com/bitchat/android/ui/globe/MvtDecoderTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/globe/MvtDecoderTest.kt new file mode 100644 index 00000000..93ae9066 --- /dev/null +++ b/app/src/test/kotlin/com/bitchat/android/ui/globe/MvtDecoderTest.kt @@ -0,0 +1,237 @@ +package com.bitchat.android.ui.globe + +import java.io.ByteArrayOutputStream +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class MvtDecoderTest { + @Test + fun decodesShortbreadGlobeLayersAndLocalizedNames() { + val tile = GlobeTileKey(2, 2, 1) + val bytes = message( + bytesField(3, oceanLayer()), + bytesField(3, boundaryLayer()), + bytesField(3, boundaryLabelLayer()), + bytesField(3, placeLabelLayer()) + ) + + val decoded = MvtDecoder(preferredLanguage = "de").decode(bytes, tile) + + assertEquals(1, decoded.oceanPolygons.size) + assertEquals(1, decoded.oceanPolygons.single().rings.size) + assertEquals(4, decoded.oceanPolygons.single().rings.single().size) + assertEquals(true, decoded.oceanPolygons.single().rings.single().isMvtExterior) + + val border = decoded.borders.single() + assertEquals(2, border.adminLevel) + assertFalse(border.maritime) + assertTrue(border.disputed) + assertEquals(2, border.ring.size) + + val country = decoded.boundaryLabels.single() + assertEquals("Testland", country.name) + assertEquals(MapLabelKind.COUNTRY, country.kind) + assertEquals(8_000_000L, country.importance) + + val capital = decoded.placeLabels.single() + assertEquals("Prüfstadt", capital.name) + assertEquals(MapLabelKind.CAPITAL, capital.kind) + assertEquals(3_700_000L, capital.importance) + assertTrue(capital.lat in -85.1f..85.1f) + assertTrue(capital.lon in -180f..180f) + } + + @Test(expected = MvtDecodingException::class) + fun rejectsOversizedTilesBeforeParsing() { + MvtDecoder().decode( + ByteArray(MvtDecoder.MAX_TILE_BYTES + 1), + GlobeTileKey(0, 0, 0) + ) + } + + @Test + fun skipsUnsupportedLayersBeforeParsingTheirFeatures() { + val malformedFeature = byteArrayOf(0) + val bytes = message( + bytesField( + 3, + layer( + name = "streets", + keys = emptyList(), + values = emptyList(), + features = listOf(malformedFeature) + ) + ), + bytesField(3, oceanLayer()) + ) + + val decoded = MvtDecoder().decode(bytes, GlobeTileKey(0, 0, 0)) + + assertEquals(1, decoded.oceanPolygons.size) + } + + private fun oceanLayer(): ByteArray { + val geometry = intArrayOf( + command(MOVE_TO, 1), + zigZag(200), + zigZag(200), + command(LINE_TO, 3), + zigZag(3000), + zigZag(0), + zigZag(0), + zigZag(3000), + zigZag(-3000), + zigZag(0), + command(CLOSE_PATH, 1) + ) + return layer( + name = "ocean", + keys = emptyList(), + values = emptyList(), + features = listOf(feature(type = 3, geometry = geometry)) + ) + } + + private fun boundaryLayer(): ByteArray { + val keys = listOf("admin_level", "maritime", "disputed") + val values = listOf(uintValue(2), boolValue(false), boolValue(true)) + val geometry = intArrayOf( + command(MOVE_TO, 1), + zigZag(400), + zigZag(1000), + command(LINE_TO, 1), + zigZag(2500), + zigZag(0) + ) + return layer( + name = "boundaries", + keys = keys, + values = values, + features = listOf( + feature( + tags = intArrayOf(0, 0, 1, 1, 2, 2), + type = 2, + geometry = geometry + ) + ) + ) + } + + private fun boundaryLabelLayer(): ByteArray { + val keys = listOf("admin_level", "name", "way_area") + val values = listOf(uintValue(2), stringValue("Testland"), uintValue(8_000_000)) + return layer( + name = "boundary_labels", + keys = keys, + values = values, + features = listOf( + feature( + tags = intArrayOf(0, 0, 1, 1, 2, 2), + type = 1, + geometry = pointGeometry(1900, 1800) + ) + ) + ) + } + + private fun placeLabelLayer(): ByteArray { + val keys = listOf("name", "name_de", "kind", "population") + val values = listOf( + stringValue("Test City"), + stringValue("Prüfstadt"), + stringValue("capital"), + uintValue(3_700_000) + ) + return layer( + name = "place_labels", + keys = keys, + values = values, + features = listOf( + feature( + tags = intArrayOf(0, 0, 1, 1, 2, 2, 3, 3), + type = 1, + geometry = pointGeometry(2100, 2200) + ) + ) + ) + } + + private fun layer( + name: String, + keys: List, + values: List, + features: List + ): ByteArray = message( + varintField(15, 2), + bytesField(1, name.encodeToByteArray()), + *features.map { bytesField(2, it) }.toTypedArray(), + *keys.map { bytesField(3, it.encodeToByteArray()) }.toTypedArray(), + *values.map { bytesField(4, it) }.toTypedArray(), + varintField(5, EXTENT.toLong()) + ) + + private fun feature( + tags: IntArray = intArrayOf(), + type: Int, + geometry: IntArray + ): ByteArray = message( + if (tags.isEmpty()) byteArrayOf() else bytesField(2, packed(tags)), + varintField(3, type.toLong()), + bytesField(4, packed(geometry)) + ) + + private fun pointGeometry(x: Int, y: Int): IntArray = intArrayOf( + command(MOVE_TO, 1), + zigZag(x), + zigZag(y) + ) + + private fun stringValue(value: String): ByteArray = + bytesField(1, value.encodeToByteArray()) + + private fun uintValue(value: Long): ByteArray = varintField(5, value) + + private fun boolValue(value: Boolean): ByteArray = + varintField(7, if (value) 1 else 0) + + private fun bytesField(number: Int, bytes: ByteArray): ByteArray = + message(varint((number shl 3 or 2).toLong()), varint(bytes.size.toLong()), bytes) + + private fun varintField(number: Int, value: Long): ByteArray = + message(varint((number shl 3).toLong()), varint(value)) + + private fun packed(values: IntArray): ByteArray = + message(*values.map { varint(it.toLong()) }.toTypedArray()) + + private fun command(id: Int, count: Int): Int = count shl 3 or id + + private fun zigZag(value: Int): Int = (value shl 1) xor (value shr 31) + + private fun message(vararg pieces: ByteArray): ByteArray { + val output = ByteArrayOutputStream() + pieces.forEach(output::write) + return output.toByteArray() + } + + private fun varint(value: Long): ByteArray { + var remaining = value + val output = ByteArrayOutputStream() + while (true) { + if ((remaining and -128L) == 0L) { + output.write(remaining.toInt()) + return output.toByteArray() + } + output.write((remaining.toInt() and 0x7f) or 0x80) + remaining = remaining ushr 7 + } + } + + companion object { + private const val EXTENT = 4096 + private const val MOVE_TO = 1 + private const val LINE_TO = 2 + private const val CLOSE_PATH = 7 + } +} From 033e224bc72d38823aecd1b23c31501771d9e54f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 1 Aug 2026 01:45:25 +0200 Subject: [PATCH 2/2] Preserve complete globe map during partial loads --- .../android/ui/GeohashPickerActivity.kt | 50 ++++++++-------- .../bitchat/android/ui/globe/GlobeMapData.kt | 58 ++++++++++++++++++ .../ui/globe/GlobeMapPresentationStateTest.kt | 59 +++++++++++++++++++ 3 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeMapPresentationStateTest.kt diff --git a/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt b/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt index 89b560ce..470e363f 100644 --- a/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt +++ b/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt @@ -38,6 +38,7 @@ import com.bitchat.android.geohash.Geohash import com.bitchat.android.geohash.GeohashChannelLevel import com.bitchat.android.geohash.LocationChannelManager import com.bitchat.android.ui.globe.GlobeColors +import com.bitchat.android.ui.globe.GlobeMapPresentationState import com.bitchat.android.ui.globe.GlobeMapUiState import com.bitchat.android.ui.globe.GlobeState import com.bitchat.android.ui.globe.GlobeTileRequest @@ -124,28 +125,29 @@ class GeohashPickerActivity : OrientationAwareActivity() { DisposableEffect(mapRepository) { onDispose { mapRepository.close() } } - var mapUiState by remember { mutableStateOf(GlobeMapUiState()) } + var mapPresentationState by remember { + mutableStateOf(GlobeMapPresentationState()) + } var mapRetryNonce by remember { mutableIntStateOf(0) } LaunchedEffect(globeState, mapRepository, mapRetryNonce) { - if (mapUiState.data.oceanPolygons.isEmpty()) { - mapUiState = mapUiState.copy(isLoading = true, hasError = false) + if (mapPresentationState.uiState.data.oceanPolygons.isEmpty()) { + mapPresentationState = mapPresentationState.startLoading() try { val overview = mapRepository.load( GlobeTileRequest(detailZoom = 0, detailTiles = emptySet()) ) - mapUiState = GlobeMapUiState( - data = overview.data, - isLoading = false, - hasError = false + mapPresentationState = mapPresentationState.copy( + uiState = GlobeMapUiState( + data = overview.data, + isLoading = false, + hasError = false + ) ) } catch (cancellation: CancellationException) { throw cancellation } catch (_: Exception) { - mapUiState = mapUiState.copy( - isLoading = false, - hasError = true - ) + mapPresentationState = mapPresentationState.showError() return@LaunchedEffect } } @@ -171,36 +173,30 @@ class GeohashPickerActivity : OrientationAwareActivity() { // in the flow (instead of filtering it) immediately cancels any // obsolete HTTP/decode batch. if (request == null) { - mapUiState = mapUiState.copy(isLoading = false) + mapPresentationState = + mapPresentationState.cancelLoading() return@collectLatest } // Let fast drag/zoom changes settle before starting another batch. delay(MAP_REQUEST_SETTLE_MS) - mapUiState = mapUiState.copy(isLoading = true, hasError = false) + mapPresentationState = mapPresentationState.startLoading() try { val result = mapRepository.load(request) { partial -> - mapUiState = GlobeMapUiState( - data = partial.data, - isLoading = true, - hasError = false - ) + mapPresentationState = + mapPresentationState.showPartial(partial) } - mapUiState = GlobeMapUiState( - data = result.data, - isLoading = false, - hasError = result.failedTileCount > 0 - ) + mapPresentationState = + mapPresentationState.showComplete(result) } catch (cancellation: CancellationException) { throw cancellation } catch (_: Exception) { - mapUiState = mapUiState.copy( - isLoading = false, - hasError = true - ) + mapPresentationState = mapPresentationState.showError() } } } + val mapUiState = mapPresentationState.uiState + val colorScheme = MaterialTheme.colorScheme val dark = colorScheme.background.luminance() < 0.5f val globeColors = remember(colorScheme, dark) { diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt index 65a08ea8..653756ee 100644 --- a/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeMapData.kt @@ -195,3 +195,61 @@ data class GlobeMapUiState( val isLoading: Boolean = false, val hasError: Boolean = false ) + +/** + * Keeps progressive tile updates from replacing a complete map with a priority-only + * subset. The first request may still render progressively; after one full successful + * detail request, cancellation and tile failures retain that complete snapshot. + */ +internal data class GlobeMapPresentationState( + val uiState: GlobeMapUiState = GlobeMapUiState(), + val lastCompleteData: GlobeMapData? = null +) { + fun startLoading(): GlobeMapPresentationState = copy( + uiState = uiState.copy(isLoading = true, hasError = false) + ) + + fun showPartial(result: GlobeMapLoadResult): GlobeMapPresentationState = copy( + uiState = GlobeMapUiState( + data = lastCompleteData ?: result.data, + isLoading = true, + hasError = false + ) + ) + + fun showComplete(result: GlobeMapLoadResult): GlobeMapPresentationState { + val completedWithoutFailures = result.failedTileCount == 0 + val displayedData = if (completedWithoutFailures) { + result.data + } else { + lastCompleteData ?: result.data + } + return copy( + uiState = GlobeMapUiState( + data = displayedData, + isLoading = false, + hasError = !completedWithoutFailures + ), + lastCompleteData = if (completedWithoutFailures) { + result.data + } else { + lastCompleteData + } + ) + } + + fun cancelLoading(): GlobeMapPresentationState = copy( + uiState = uiState.copy( + data = lastCompleteData ?: uiState.data, + isLoading = false + ) + ) + + fun showError(): GlobeMapPresentationState = copy( + uiState = uiState.copy( + data = lastCompleteData ?: uiState.data, + isLoading = false, + hasError = true + ) + ) +} diff --git a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeMapPresentationStateTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeMapPresentationStateTest.kt new file mode 100644 index 00000000..cb1f91ec --- /dev/null +++ b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeMapPresentationStateTest.kt @@ -0,0 +1,59 @@ +package com.bitchat.android.ui.globe + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Test + +class GlobeMapPresentationStateTest { + @Test + fun partialUpdateAfterCompleteLoadKeepsCompleteMapThroughCancellation() { + val completeData = GlobeMapData(detailZoom = 6) + val priorityOnlyData = GlobeMapData(detailZoom = 7) + var state = GlobeMapPresentationState().showComplete( + result(completeData) + ) + + state = state.startLoading().showPartial(result(priorityOnlyData)) + + assertSame(completeData, state.uiState.data) + assertTrue(state.uiState.isLoading) + + state = state.cancelLoading() + + assertSame(completeData, state.uiState.data) + assertFalse(state.uiState.isLoading) + } + + @Test + fun failedReplacementAfterCompleteLoadKeepsCompleteMap() { + val completeData = GlobeMapData(detailZoom = 6) + val incompleteData = GlobeMapData(detailZoom = 7) + val state = GlobeMapPresentationState() + .showComplete(result(completeData)) + .showComplete(result(incompleteData, failedTileCount = 1)) + + assertSame(completeData, state.uiState.data) + assertTrue(state.uiState.hasError) + assertSame(completeData, state.lastCompleteData) + } + + @Test + fun firstLoadCanStillDisplayPriorityTilesProgressively() { + val priorityOnlyData = GlobeMapData(detailZoom = 4) + val state = GlobeMapPresentationState() + .showPartial(result(priorityOnlyData)) + + assertSame(priorityOnlyData, state.uiState.data) + assertTrue(state.uiState.isLoading) + } + + private fun result( + data: GlobeMapData, + failedTileCount: Int = 0 + ): GlobeMapLoadResult = GlobeMapLoadResult( + data = data, + requestedTileCount = 5, + failedTileCount = failedTileCount + ) +}