├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── example-browser ├── index.html ├── leaflet-tooltip-layout.dist.js ├── main.js └── styles.css ├── example ├── Config.js ├── Icon.js ├── TestData.js ├── images │ ├── circle-grey.png │ └── circle-red.png ├── index.html ├── main.js └── styles.css ├── lib └── index.js ├── package-lock.json ├── package.json └── prettier.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 9 | } 10 | } 11 | ], 12 | "stage-2" 13 | ], 14 | "plugins": ["transform-runtime"] 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /node_modules/ 6 | src/components/CitySelect 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true 9 | }, 10 | rules: { 11 | 'arrow-parens': 0, 12 | 'generator-star-spacing': 0, 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 14 | 'space-before-function-paren': 0, 15 | semi: 0 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,intellij+all,visualstudio,visualstudiocode 3 | 4 | ### Intellij+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/modules.xml 36 | # .idea/*.iml 37 | # .idea/modules 38 | 39 | # CMake 40 | cmake-build-*/ 41 | 42 | # Mongo Explorer plugin 43 | .idea/**/mongoSettings.xml 44 | 45 | # File-based project format 46 | *.iws 47 | 48 | # IntelliJ 49 | out/ 50 | 51 | # mpeltonen/sbt-idea plugin 52 | .idea_modules/ 53 | 54 | # JIRA plugin 55 | atlassian-ide-plugin.xml 56 | 57 | # Cursive Clojure plugin 58 | .idea/replstate.xml 59 | 60 | # Crashlytics plugin (for Android Studio and IntelliJ) 61 | com_crashlytics_export_strings.xml 62 | crashlytics.properties 63 | crashlytics-build.properties 64 | fabric.properties 65 | 66 | # Editor-based Rest Client 67 | .idea/httpRequests 68 | 69 | ### Intellij+all Patch ### 70 | # Ignores the whole .idea folder and all .iml files 71 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 72 | 73 | .idea/ 74 | 75 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 76 | 77 | *.iml 78 | modules.xml 79 | .idea/misc.xml 80 | *.ipr 81 | 82 | ### Linux ### 83 | *~ 84 | 85 | # temporary files which can be created if a process still has a handle open of a deleted file 86 | .fuse_hidden* 87 | 88 | # KDE directory preferences 89 | .directory 90 | 91 | # Linux trash folder which might appear on any partition or disk 92 | .Trash-* 93 | 94 | # .nfs files are created when an open file is removed but is still being accessed 95 | .nfs* 96 | 97 | ### macOS ### 98 | # General 99 | .DS_Store 100 | .AppleDouble 101 | .LSOverride 102 | 103 | # Icon must end with two \r 104 | Icon 105 | 106 | # Thumbnails 107 | ._* 108 | 109 | # Files that might appear in the root of a volume 110 | .DocumentRevisions-V100 111 | .fseventsd 112 | .Spotlight-V100 113 | .TemporaryItems 114 | .Trashes 115 | .VolumeIcon.icns 116 | .com.apple.timemachine.donotpresent 117 | 118 | # Directories potentially created on remote AFP share 119 | .AppleDB 120 | .AppleDesktop 121 | Network Trash Folder 122 | Temporary Items 123 | .apdisk 124 | 125 | ### Node ### 126 | # Logs 127 | logs 128 | *.log 129 | npm-debug.log* 130 | yarn-debug.log* 131 | yarn-error.log* 132 | 133 | # Runtime data 134 | pids 135 | *.pid 136 | *.seed 137 | *.pid.lock 138 | 139 | # Directory for instrumented libs generated by jscoverage/JSCover 140 | lib-cov 141 | 142 | # Coverage directory used by tools like istanbul 143 | coverage 144 | 145 | # nyc test coverage 146 | .nyc_output 147 | 148 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 149 | .grunt 150 | 151 | # Bower dependency directory (https://bower.io/) 152 | bower_components 153 | 154 | # node-waf configuration 155 | .lock-wscript 156 | 157 | # Compiled binary addons (https://nodejs.org/api/addons.html) 158 | build/Release 159 | 160 | # Dependency directories 161 | node_modules/ 162 | jspm_packages/ 163 | 164 | # TypeScript v1 declaration files 165 | typings/ 166 | 167 | # Optional npm cache directory 168 | .npm 169 | 170 | # Optional eslint cache 171 | .eslintcache 172 | 173 | # Optional REPL history 174 | .node_repl_history 175 | 176 | # Output of 'npm pack' 177 | *.tgz 178 | 179 | # Yarn Integrity file 180 | .yarn-integrity 181 | 182 | # dotenv environment variables file 183 | .env 184 | 185 | # parcel-bundler cache (https://parceljs.org/) 186 | .cache 187 | 188 | # next.js build output 189 | .next 190 | 191 | # nuxt.js build output 192 | .nuxt 193 | 194 | # vuepress build output 195 | .vuepress/dist 196 | 197 | # Serverless directories 198 | .serverless 199 | 200 | ### VisualStudioCode ### 201 | .vscode/* 202 | !.vscode/settings.json 203 | !.vscode/tasks.json 204 | !.vscode/launch.json 205 | !.vscode/extensions.json 206 | 207 | ### Windows ### 208 | # Windows thumbnail cache files 209 | Thumbs.db 210 | ehthumbs.db 211 | ehthumbs_vista.db 212 | 213 | # Dump file 214 | *.stackdump 215 | 216 | # Folder config file 217 | [Dd]esktop.ini 218 | 219 | # Recycle Bin used on file shares 220 | $RECYCLE.BIN/ 221 | 222 | # Windows Installer files 223 | *.cab 224 | *.msi 225 | *.msix 226 | *.msm 227 | *.msp 228 | 229 | # Windows shortcuts 230 | *.lnk 231 | 232 | ### VisualStudio ### 233 | ## Ignore Visual Studio temporary files, build results, and 234 | ## files generated by popular Visual Studio add-ons. 235 | ## 236 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 237 | 238 | # User-specific files 239 | *.suo 240 | *.user 241 | *.userosscache 242 | *.sln.docstates 243 | 244 | # User-specific files (MonoDevelop/Xamarin Studio) 245 | *.userprefs 246 | 247 | # Build results 248 | [Dd]ebug/ 249 | [Dd]ebugPublic/ 250 | [Rr]elease/ 251 | [Rr]eleases/ 252 | x64/ 253 | x86/ 254 | bld/ 255 | [Bb]in/ 256 | [Oo]bj/ 257 | [Ll]og/ 258 | 259 | # Visual Studio 2015/2017 cache/options directory 260 | .vs/ 261 | # Uncomment if you have tasks that create the project's static files in wwwroot 262 | #wwwroot/ 263 | 264 | # Visual Studio 2017 auto generated files 265 | Generated\ Files/ 266 | 267 | # MSTest test Results 268 | [Tt]est[Rr]esult*/ 269 | [Bb]uild[Ll]og.* 270 | 271 | # NUNIT 272 | *.VisualState.xml 273 | TestResult.xml 274 | 275 | # Build Results of an ATL Project 276 | [Dd]ebugPS/ 277 | [Rr]eleasePS/ 278 | dlldata.c 279 | 280 | # Benchmark Results 281 | BenchmarkDotNet.Artifacts/ 282 | 283 | # .NET Core 284 | project.lock.json 285 | project.fragment.lock.json 286 | artifacts/ 287 | 288 | # StyleCop 289 | StyleCopReport.xml 290 | 291 | # Files built by Visual Studio 292 | *_i.c 293 | *_p.c 294 | *_h.h 295 | *.ilk 296 | *.meta 297 | *.obj 298 | *.iobj 299 | *.pch 300 | *.pdb 301 | *.ipdb 302 | *.pgc 303 | *.pgd 304 | *.rsp 305 | *.sbr 306 | *.tlb 307 | *.tli 308 | *.tlh 309 | *.tmp 310 | *.tmp_proj 311 | *.vspscc 312 | *.vssscc 313 | .builds 314 | *.pidb 315 | *.svclog 316 | *.scc 317 | 318 | # Chutzpah Test files 319 | _Chutzpah* 320 | 321 | # Visual C++ cache files 322 | ipch/ 323 | *.aps 324 | *.ncb 325 | *.opendb 326 | *.opensdf 327 | *.sdf 328 | *.cachefile 329 | *.VC.db 330 | *.VC.VC.opendb 331 | 332 | # Visual Studio profiler 333 | *.psess 334 | *.vsp 335 | *.vspx 336 | *.sap 337 | 338 | # Visual Studio Trace Files 339 | *.e2e 340 | 341 | # TFS 2012 Local Workspace 342 | $tf/ 343 | 344 | # Guidance Automation Toolkit 345 | *.gpState 346 | 347 | # ReSharper is a .NET coding add-in 348 | _ReSharper*/ 349 | *.[Rr]e[Ss]harper 350 | *.DotSettings.user 351 | 352 | # JustCode is a .NET coding add-in 353 | .JustCode 354 | 355 | # TeamCity is a build add-in 356 | _TeamCity* 357 | 358 | # DotCover is a Code Coverage Tool 359 | *.dotCover 360 | 361 | # AxoCover is a Code Coverage Tool 362 | .axoCover/* 363 | !.axoCover/settings.json 364 | 365 | # Visual Studio code coverage results 366 | *.coverage 367 | *.coveragexml 368 | 369 | # NCrunch 370 | _NCrunch_* 371 | .*crunch*.local.xml 372 | nCrunchTemp_* 373 | 374 | # MightyMoose 375 | *.mm.* 376 | AutoTest.Net/ 377 | 378 | # Web workbench (sass) 379 | .sass-cache/ 380 | 381 | # Installshield output folder 382 | [Ee]xpress/ 383 | 384 | # DocProject is a documentation generator add-in 385 | DocProject/buildhelp/ 386 | DocProject/Help/*.HxT 387 | DocProject/Help/*.HxC 388 | DocProject/Help/*.hhc 389 | DocProject/Help/*.hhk 390 | DocProject/Help/*.hhp 391 | DocProject/Help/Html2 392 | DocProject/Help/html 393 | 394 | # Click-Once directory 395 | publish/ 396 | 397 | # Publish Web Output 398 | *.[Pp]ublish.xml 399 | *.azurePubxml 400 | # Note: Comment the next line if you want to checkin your web deploy settings, 401 | # but database connection strings (with potential passwords) will be unencrypted 402 | *.pubxml 403 | *.publishproj 404 | 405 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 406 | # checkin your Azure Web App publish settings, but sensitive information contained 407 | # in these scripts will be unencrypted 408 | PublishScripts/ 409 | 410 | # NuGet Packages 411 | *.nupkg 412 | # The packages folder can be ignored because of Package Restore 413 | **/[Pp]ackages/* 414 | # except build/, which is used as an MSBuild target. 415 | !**/[Pp]ackages/build/ 416 | # Uncomment if necessary however generally it will be regenerated when needed 417 | #!**/[Pp]ackages/repositories.config 418 | # NuGet v3's project.json files produces more ignorable files 419 | *.nuget.props 420 | *.nuget.targets 421 | 422 | # Microsoft Azure Build Output 423 | csx/ 424 | *.build.csdef 425 | 426 | # Microsoft Azure Emulator 427 | ecf/ 428 | rcf/ 429 | 430 | # Windows Store app package directories and files 431 | AppPackages/ 432 | BundleArtifacts/ 433 | Package.StoreAssociation.xml 434 | _pkginfo.txt 435 | *.appx 436 | 437 | # Visual Studio cache files 438 | # files ending in .cache can be ignored 439 | *.[Cc]ache 440 | # but keep track of directories ending in .cache 441 | !*.[Cc]ache/ 442 | 443 | # Others 444 | ClientBin/ 445 | ~$* 446 | *.dbmdl 447 | *.dbproj.schemaview 448 | *.jfm 449 | *.pfx 450 | *.publishsettings 451 | orleans.codegen.cs 452 | 453 | # Including strong name files can present a security risk 454 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 455 | #*.snk 456 | 457 | # Since there are multiple workflows, uncomment next line to ignore bower_components 458 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 459 | #bower_components/ 460 | 461 | # RIA/Silverlight projects 462 | Generated_Code/ 463 | 464 | # Backup & report files from converting an old project file 465 | # to a newer Visual Studio version. Backup files are not needed, 466 | # because we have git ;-) 467 | _UpgradeReport_Files/ 468 | Backup*/ 469 | UpgradeLog*.XML 470 | UpgradeLog*.htm 471 | ServiceFabricBackup/ 472 | *.rptproj.bak 473 | 474 | # SQL Server files 475 | *.mdf 476 | *.ldf 477 | *.ndf 478 | 479 | # Business Intelligence projects 480 | *.rdl.data 481 | *.bim.layout 482 | *.bim_*.settings 483 | *.rptproj.rsuser 484 | 485 | # Microsoft Fakes 486 | FakesAssemblies/ 487 | 488 | # GhostDoc plugin setting file 489 | *.GhostDoc.xml 490 | 491 | # Node.js Tools for Visual Studio 492 | .ntvs_analysis.dat 493 | 494 | # Visual Studio 6 build log 495 | *.plg 496 | 497 | # Visual Studio 6 workspace options file 498 | *.opt 499 | 500 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 501 | *.vbw 502 | 503 | # Visual Studio LightSwitch build output 504 | **/*.HTMLClient/GeneratedArtifacts 505 | **/*.DesktopClient/GeneratedArtifacts 506 | **/*.DesktopClient/ModelManifest.xml 507 | **/*.Server/GeneratedArtifacts 508 | **/*.Server/ModelManifest.xml 509 | _Pvt_Extensions 510 | 511 | # Paket dependency manager 512 | .paket/paket.exe 513 | paket-files/ 514 | 515 | # FAKE - F# Make 516 | .fake/ 517 | 518 | # JetBrains Rider 519 | *.sln.iml 520 | 521 | # CodeRush 522 | .cr/ 523 | 524 | # Python Tools for Visual Studio (PTVS) 525 | __pycache__/ 526 | *.pyc 527 | 528 | # Cake - Uncomment if you are using it 529 | # tools/** 530 | # !tools/packages.config 531 | 532 | # Tabs Studio 533 | *.tss 534 | 535 | # Telerik's JustMock configuration file 536 | *.jmconfig 537 | 538 | # BizTalk build output 539 | *.btp.cs 540 | *.btm.cs 541 | *.odx.cs 542 | *.xsd.cs 543 | 544 | # OpenCover UI analysis results 545 | OpenCover/ 546 | 547 | # Azure Stream Analytics local run output 548 | ASALocalRun/ 549 | 550 | # MSBuild Binary and Structured Log 551 | *.binlog 552 | 553 | # NVidia Nsight GPU debugger configuration file 554 | *.nvuser 555 | 556 | # MFractors (Xamarin productivity tool) working folder 557 | .mfractor/ 558 | 559 | # Local History for Visual Studio 560 | .localhistory/ 561 | 562 | 563 | # End of https://www.gitignore.io/api/node,linux,macos,windows,intellij+all,visualstudio,visualstudiocode 564 | 565 | # custom rules 566 | dist/ 567 | 568 | .cache/ 569 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .cache 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 ZijingP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leaflet-tooltip-layout 2 | 3 | This plugin is designed to avoid tooltip overlapping and make users find out the relationship between each tooltip and marker easily. It is based on [Force-Directed Drawing Algorithms](http://cs.brown.edu/people/rtamassi/gdhandbook/chapters/force-directed.pdf) in the chapter 12 of the book Handbook of Graph Drawing and Visualization written by Stephen G. Kobourov. 4 | 5 | [Here is the demo](https://zijingpeng.github.io/overlapping-avoided-tooltip/) 6 | 7 | 8 | 9 | ## Installation 10 | 11 | ```shell 12 | npm i leaflet-tooltip-layout --save 13 | # or 14 | yarn add leaflet-tooltip-layout 15 | ``` 16 | 17 | Or you can just copy `./lib/index.js` to your project and rename it to what you want. 18 | 19 | 20 | 21 | ## Getting Started 22 | 23 | ### *ES6* 24 | 25 | ```js 26 | import * as tooltipLayout from 'leaflet-tooltip-layout'; 27 | // or 28 | import { resetMarker, getMarkers, getLine, initialize, getLine } from 'leaflet-tooltip-layout'; 29 | ``` 30 | 31 | 32 | 33 | ### *CommonJS* 34 | 35 | ```js 36 | const tooltipLayout = require('leaflet-tooltip-layout'); 37 | ``` 38 | 39 | 40 | 41 | ### *Browser* 42 | 43 | Make sure `leaflet` is imported before this plugin, and `window.L` is available 44 | 45 | ```html 46 | 47 | ``` 48 | 49 | 50 | 51 | ## API Reference 52 | 53 | 1. `L.tooltipLayout.resetMarker(marker)` 54 | 55 | Create the marker, bind tooltip to the marker, then use this function. 56 | 57 | Usage example: 58 | 59 | ```js 60 | var marker = L.marker(coord, { 61 | icon: icon 62 | }).addTo(map); 63 | marker.bindTooltip('Hello world!'); 64 | L.tooltipLayout.resetMarker(marker); 65 | ``` 66 | 67 | 2. `L.tooltipLayout.getMarkers()` 68 | 69 | Get the all the markers in this layout. 70 | 71 | Usage example: 72 | 73 | ```js 74 | var markerList = getMarkers(); 75 | for (i = 0; i < markerList.length; i++) { 76 | marker = markerList[i]; 77 | tooltip = marker.getTooltip(); 78 | marker._icon.addEventListener('mouseover', function (){ 79 | // your code 80 | }); 81 | tooltip._container.addEventListener('mouseover', function (){ 82 | // your code 83 | }); 84 | } 85 | ``` 86 | 87 | 3. `L.tooltipLayout.getLine(marker)` 88 | 89 | Get the line between one marker and its tooltip. 90 | 91 | 4. `L.tooltipLayout.initialize(map, onPolylineCreated)` 92 | 93 | After adding all the markers and tooltips, use this function to create the layout. 94 | 95 | `onPolylineCreated` is a callback function that allows you to define the style of the line between markers and tooltips, if you want the default one, let this parameter `null`. 96 | 97 | Or you can define the function like this: 98 | 99 | ```js 100 | function onPolylineCreated(ply) { 101 | ply.setStyle({ 102 | color: '#40809d' 103 | }) 104 | } 105 | ``` 106 | 107 | 5. `L.tooltipLayout.addMarker(marker)` 108 | 109 | Add new marker to internal marker list 110 | 111 | 6. `L.tooltipLayout.deleteMarker(marker)` 112 | 113 | Delete specific marker from internal marker list 114 | 115 | 7. `L.tooltipLayout.resetMapContent()` 116 | 117 | Rmove all exisiting markers, tooltips, lines from the map. 118 | If content is dynamically generated to the map, for example realtime contents. It is recommanded to use this function to clear all contents and regenterated content again. 119 | 120 | 121 | 122 | ## Build Guide 123 | 124 | ```shell 125 | git clone git@github.com:ZijingPeng/leaflet-tooltip-layout.git 126 | cd ./leaflet-tooltip-layout 127 | 128 | npm i # install dependencies 129 | npm run build # build lib & example 130 | 131 | # or 132 | npm run serve # enter dev zone 133 | ``` 134 | 135 | 136 | 137 | ## License 138 | 139 | MIT License 140 | -------------------------------------------------------------------------------- /example-browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | leaflet-plugin-tooltip-layout DEMO 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example-browser/leaflet-tooltip-layout.dist.js: -------------------------------------------------------------------------------- 1 | (function(factory, window) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['leaflet'], factory); 4 | } else if (typeof exports === 'object') { 5 | module.exports = factory(require('leaflet')); 6 | } 7 | if (typeof window !== 'undefined' && window.L) { 8 | window.L.tooltipLayout = factory(L); 9 | } 10 | })(function(L) { 11 | var TooltipLayout = {}; 12 | 13 | // global variables 14 | let map; 15 | let markerList = []; // all markers here 16 | let polylineList = []; // all polylines here 17 | 18 | // events 19 | let _onPolylineCreated = null; // will be called after polyline has been created 20 | 21 | function initialize(leafletMap, onPolylineCreated) { 22 | map = leafletMap; 23 | polylineList = []; 24 | 25 | //default style 26 | if (onPolylineCreated) { 27 | _onPolylineCreated = onPolylineCreated; 28 | } else { 29 | _onPolylineCreated = ply => { 30 | ply.setStyle({ 31 | color: '#90A4AE' 32 | }); 33 | }; 34 | } 35 | 36 | setRandomPos(map); 37 | layoutByForce(); 38 | setEdgePosition(); 39 | drawLine(map); 40 | 41 | // event registrations 42 | map.on('zoomstart', function() { 43 | removeAllPolyline(map); 44 | }); 45 | 46 | map.on('zoomend', function() { 47 | setRandomPos(map); 48 | layoutByForce(); 49 | setEdgePosition(); 50 | drawLine(map); 51 | }); 52 | 53 | map.on('dragend', function() { 54 | removeAllPolyline(map); 55 | setRandomPos(map); 56 | layoutByForce(); 57 | setEdgePosition(); 58 | drawLine(map); 59 | }); 60 | 61 | map.on('resize', function() { 62 | removeAllPolyline(map); 63 | setRandomPos(map); 64 | layoutByForce(); 65 | setEdgePosition(); 66 | drawLine(map); 67 | }); 68 | } 69 | 70 | function resetMarker(marker) { 71 | var name = marker.getTooltip().getContent(); 72 | var options = marker.getTooltip().options; 73 | marker.unbindTooltip(); 74 | 75 | marker.bindTooltip(name, { 76 | pane: options.pane, 77 | offset: options.offset, 78 | classname: 'heading', 79 | permanent: true, 80 | interactive: true, 81 | direction: 'left', 82 | sticky: 'none', 83 | opacity: options.opacity 84 | }); 85 | markerList.push(marker); 86 | } 87 | 88 | function getMarkers() { 89 | return markerList; 90 | } 91 | 92 | function getLine(marker) { 93 | return marker.__ply; 94 | } 95 | 96 | function removeAllPolyline(map) { 97 | var i; 98 | for (i = 0; i < polylineList.length; i++) { 99 | map.removeLayer(polylineList[i]); 100 | } 101 | polylineList = []; 102 | } 103 | 104 | /** 105 | * Draw lines between markers and tooltips 106 | * @param map leaflet map 107 | */ 108 | function drawLine(map) { 109 | removeAllPolyline(map); 110 | for (var i = 0; i < markerList.length; i++) { 111 | var marker = markerList[i]; 112 | var markerDom = marker._icon; 113 | var markerPosition = getPosition(markerDom); 114 | var label = marker.getTooltip(); 115 | 116 | var labelDom = label._container; 117 | var labelPosition = getPosition(labelDom); 118 | 119 | var x1 = labelPosition.x; 120 | var y1 = labelPosition.y; 121 | 122 | var x = markerPosition.x; 123 | var y = markerPosition.y; 124 | 125 | x1 -= 5; 126 | y1 += 2; 127 | if (x1 - x !== 0 || y1 - y !== 0) { 128 | if (x1 + labelDom.offsetWidth < markerPosition.x) { 129 | x1 += labelDom.offsetWidth; 130 | } 131 | if (y1 + labelDom.offsetHeight < markerPosition.y) { 132 | y1 += labelDom.offsetHeight; 133 | } 134 | var lineDest = L.point(x1, y1); 135 | var destLatLng = map.layerPointToLatLng(lineDest); 136 | 137 | setTimeout( 138 | ((marker, destLatLng) => () => { 139 | let ply = L.polyline([marker.getLatLng(), destLatLng]); 140 | _onPolylineCreated && _onPolylineCreated(ply); 141 | marker.__ply = ply; 142 | polylineList.push(ply); 143 | ply.addTo(map); 144 | })(marker, destLatLng), 145 | 0 146 | ); 147 | } 148 | } 149 | } 150 | 151 | function setRandomPos() { 152 | for (var i = 0; i < markerList.length; i++) { 153 | var marker = markerList[i]; 154 | var label = marker.getTooltip(); 155 | var labelDom = label._container; 156 | var markerDom = marker._icon; 157 | var markerPosition = getPosition(markerDom); 158 | // var angle = Math.floor(Math.random() * 19 + 1) * 2 * Math.PI / 20; 159 | var angle = ((2 * Math.PI) / 6) * i; 160 | var x = markerPosition.x; 161 | var y = markerPosition.y; 162 | var dest = L.point( 163 | Math.ceil(x + 50 * Math.sin(angle)), 164 | Math.ceil(y + 50 * Math.cos(angle)) 165 | ); 166 | L.DomUtil.setPosition(labelDom, dest); 167 | } 168 | } 169 | 170 | function scaleTo(a, b) { 171 | return L.point(a.x * b.x, a.y * b.y); 172 | } 173 | 174 | function normalize(a) { 175 | var l = a.distanceTo(L.point(0, 0)); 176 | if (l === 0) { 177 | return a; 178 | } 179 | return L.point(a.x / l, a.y / l); 180 | } 181 | 182 | function fa(x, k) { 183 | return (x * x) / k; 184 | } 185 | 186 | function fr(x, k) { 187 | return (k * k) / x; 188 | } 189 | 190 | /** 191 | * get position form el.style.transform 192 | */ 193 | function getPosition(el) { 194 | var translateString = el.style.transform 195 | .split('(')[1] 196 | .split(')')[0] 197 | .split(','); 198 | return L.point(parseInt(translateString[0]), parseInt(translateString[1])); 199 | } 200 | 201 | /** 202 | * t is the temperature in the system 203 | */ 204 | function computePositionStep(t) { 205 | var area = (window.innerWidth * window.innerHeight) / 10; 206 | var k = Math.sqrt(area / markerList.length); 207 | var dpos = L.point(0, 0); 208 | var v_pos; 209 | var v; 210 | var i; 211 | 212 | for (i = 0; i < markerList.length; i++) { 213 | v = markerList[i]; 214 | // get position of label v 215 | v.disp = L.point(0, 0); 216 | v_pos = getPosition(v.getTooltip()._container); 217 | 218 | // compute gravitational force 219 | for (var j = 0; j < markerList.length; j++) { 220 | var u = markerList[j]; 221 | if (i !== j) { 222 | var u_pos = getPosition(u.getTooltip()._container); 223 | dpos = v_pos.subtract(u_pos); 224 | if (dpos !== 0) { 225 | v.disp = v.disp.add( 226 | normalize(dpos).multiplyBy(fr(dpos.distanceTo(L.point(0, 0)), k)) 227 | ); 228 | } 229 | } 230 | } 231 | } 232 | 233 | // compute force between marker and tooltip 234 | for (i = 0; i < markerList.length; i++) { 235 | v = markerList[i]; 236 | v_pos = getPosition(v.getTooltip()._container); 237 | dpos = v_pos.subtract(getPosition(v._icon)); 238 | v.disp = v.disp.subtract( 239 | normalize(dpos).multiplyBy(fa(dpos.distanceTo(L.point(0, 0)), k)) 240 | ); 241 | } 242 | 243 | // calculate layout 244 | for (i = 0; i < markerList.length; i++) { 245 | var disp = markerList[i].disp; 246 | var p = getPosition(markerList[i].getTooltip()._container); 247 | var d = scaleTo( 248 | normalize(disp), 249 | L.point(Math.min(Math.abs(disp.x), t), Math.min(Math.abs(disp.y), t)) 250 | ); 251 | p = p.add(d); 252 | p = L.point(Math.ceil(p.x), Math.ceil(p.y)); 253 | L.DomUtil.setTransform(markerList[i].getTooltip()._container, p); 254 | } 255 | } 256 | 257 | function layoutByForce() { 258 | var start = Math.ceil(window.innerWidth / 10); 259 | var times = 50; 260 | var t; 261 | for (var i = 0; i < times; i += 1) { 262 | t = start * (1 - i / (times - 1)); 263 | computePositionStep(t); 264 | } 265 | 266 | for (i = 0; i < markerList.length; i++) { 267 | var disp = markerList[i].disp; 268 | var p = getPosition(markerList[i].getTooltip()._container); 269 | var width = markerList[i].getTooltip()._container.offsetWidth; 270 | var height = markerList[i].getTooltip()._container.offsetHeight; 271 | p = L.point(Math.ceil(p.x - width / 2), Math.ceil(p.y - height / 2)); 272 | L.DomUtil.setTransform(markerList[i].getTooltip()._container, p); 273 | } 274 | } 275 | 276 | function setEdgePosition() { 277 | var bounds = map.getBounds(); 278 | var northWest = map.latLngToLayerPoint(bounds.getNorthWest()); 279 | var southEast = map.latLngToLayerPoint(bounds.getSouthEast()); 280 | 281 | for (let i = 0; i < markerList.length; i++) { 282 | var tooltip = getPosition(markerList[i].getTooltip()._container); 283 | var marker = getPosition(markerList[i]._icon); 284 | var width = markerList[i].getTooltip()._container.offsetWidth; 285 | var height = markerList[i].getTooltip()._container.offsetHeight; 286 | 287 | var isEdge = false; 288 | if (marker.x > northWest.x && tooltip.x < northWest.x) { 289 | tooltip.x = northWest.x; 290 | isEdge = true; 291 | } else if (marker.x < southEast.x && tooltip.x > southEast.x - width) { 292 | tooltip.x = southEast.x - width; 293 | isEdge = true; 294 | } 295 | 296 | if (marker.y > northWest.y && tooltip.y < northWest.y) { 297 | tooltip.y = northWest.y; 298 | isEdge = true; 299 | } else if (marker.y < southEast.y && tooltip.y > southEast.y - height) { 300 | tooltip.y = southEast.y - height; 301 | isEdge = true; 302 | } 303 | 304 | if (!isEdge) { 305 | if (marker.x < northWest.x && tooltip.x > northWest.x - width) { 306 | tooltip.x = northWest.x - width; 307 | } else if (marker.x > southEast.x && tooltip.x < southEast.x) { 308 | tooltip.x = southEast.x; 309 | } 310 | 311 | if (marker.y < northWest.y && tooltip.y > northWest.y - height) { 312 | tooltip.y = northWest.y - height; 313 | } else if (marker.y > southEast.y && tooltip.y < southEast.y) { 314 | tooltip.y = southEast.y; 315 | } 316 | } 317 | 318 | L.DomUtil.setTransform(markerList[i].getTooltip()._container, tooltip); 319 | } 320 | } 321 | 322 | TooltipLayout['initialize'] = initialize; 323 | TooltipLayout['resetMarker'] = resetMarker; 324 | TooltipLayout['getMarkers'] = getMarkers; 325 | TooltipLayout['getLine'] = getLine; 326 | TooltipLayout['removeAllPolyline'] = removeAllPolyline; 327 | 328 | return TooltipLayout; 329 | }, window); 330 | -------------------------------------------------------------------------------- /example-browser/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZijingPeng/leaflet-tooltip-layout/d9bf9793947b29ea26e5ba5474c4ad8c5d3b176b/example-browser/main.js -------------------------------------------------------------------------------- /example-browser/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | /* translate animation */ 7 | .leaflet-tooltip { 8 | transition: transform 0.3s ease-in-out; 9 | } 10 | 11 | /* remove arrow in tooltips */ 12 | .leaflet-tooltip-top:before, 13 | .leaflet-tooltip-bottom:before, 14 | .leaflet-tooltip-left:before, 15 | .leaflet-tooltip-right:before { 16 | visibility: hidden; 17 | } 18 | -------------------------------------------------------------------------------- /example/Config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mapTileLayerUrlTemplate: 3 | 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw' 4 | }; 5 | -------------------------------------------------------------------------------- /example/Icon.js: -------------------------------------------------------------------------------- 1 | import IconCircleGrey from './images/circle-grey.png'; 2 | import IconCircleRed from './images/circle-red.png'; 3 | 4 | export const icon = L.icon({ 5 | iconUrl: IconCircleGrey, 6 | iconSize: [10, 10] 7 | }); 8 | 9 | export const iconlarge = L.icon({ 10 | iconUrl: IconCircleRed, 11 | iconSize: [14, 14] 12 | }); 13 | -------------------------------------------------------------------------------- /example/TestData.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'features': [{ 3 | 'geometry': { 4 | 'coordinates': [-86.87529, 40.4167], 5 | 'type': 'Point' 6 | }, 7 | 'type': 'Feature', 8 | 'properties': { 9 | 'geoNameId': 4922462, 10 | 'alternateNames': null, 11 | 'toponym': 'Lafayette', 12 | 'featureCode': 'PPLA2', 13 | 'countryCode': 'US', 14 | 'featureClass': 'P', 15 | 'hierarchy': { 16 | 'features': [{ 17 | 'geometry': null, 18 | 'type': 'Feature', 19 | 'properties': { 20 | 'geoNameId': 4927230, 21 | 'toponym': 'Tippecanoe County', 22 | 'hierarchy': null, 23 | 'name': 'Lafayette', 24 | 'locationType': null, 25 | 'positions': [10], 26 | 'type': 'location' 27 | } 28 | }, { 29 | 'geometry': null, 30 | 'type': 'Feature', 31 | 'properties': { 32 | 'geoNameId': 4921868, 33 | 'toponym': 'Indiana', 34 | 'hierarchy': null, 35 | 'name': 'Lafayette', 36 | 'locationType': null, 37 | 'positions': [10], 38 | 'type': 'location' 39 | } 40 | }, { 41 | 'geometry': null, 42 | 'type': 'Feature', 43 | 'properties': { 44 | 'geoNameId': 6252001, 45 | 'toponym': 'United States', 46 | 'hierarchy': null, 47 | 'name': 'Lafayette', 48 | 'locationType': null, 49 | 'positions': [10], 50 | 'type': 'location' 51 | } 52 | }, { 53 | 'geometry': null, 54 | 'type': 'Feature', 55 | 'properties': { 56 | 'geoNameId': 6255149, 57 | 'toponym': 'North America', 58 | 'hierarchy': null, 59 | 'name': 'Lafayette', 60 | 'locationType': null, 61 | 'positions': [10], 62 | 'type': 'location' 63 | } 64 | }], 65 | 'type': 'FeatureCollection' 66 | }, 67 | 'name': 'Lafayette', 68 | 'positions': [10], 69 | 'type': 'location', 70 | 'alternates': { 71 | 'features': [{ 72 | 'geometry': { 73 | 'coordinates': [-86.87529, 40.4167], 74 | 'type': 'Point' 75 | }, 76 | 'type': 'Feature', 77 | 'properties': { 78 | 'geoNameId': 4922462, 79 | 'toponym': 'Lafayette', 80 | 'featureCode': 'PPLA2', 81 | 'countryCode': 'US', 82 | 'featureClass': 'P', 83 | 'hierarchy': { 84 | 'features': [{ 85 | 'geometry': null, 86 | 'type': 'Feature', 87 | 'properties': { 88 | 'geoNameId': 4927230, 89 | 'toponym': 'Tippecanoe County', 90 | 'hierarchy': null, 91 | 'name': 'Lafayette', 92 | 'locationType': null, 93 | 'positions': [10], 94 | 'type': 'location' 95 | } 96 | }, { 97 | 'geometry': null, 98 | 'type': 'Feature', 99 | 'properties': { 100 | 'geoNameId': 4921868, 101 | 'toponym': 'Indiana', 102 | 'hierarchy': null, 103 | 'name': 'Lafayette', 104 | 'locationType': null, 105 | 'positions': [10], 106 | 'type': 'location' 107 | } 108 | }, { 109 | 'geometry': null, 110 | 'type': 'Feature', 111 | 'properties': { 112 | 'geoNameId': 6252001, 113 | 'toponym': 'United States', 114 | 'hierarchy': null, 115 | 'name': 'Lafayette', 116 | 'locationType': null, 117 | 'positions': [10], 118 | 'type': 'location' 119 | } 120 | }, { 121 | 'geometry': null, 122 | 'type': 'Feature', 123 | 'properties': { 124 | 'geoNameId': 6255149, 125 | 'toponym': 'North America', 126 | 'hierarchy': null, 127 | 'name': 'Lafayette', 128 | 'locationType': null, 129 | 'positions': [10], 130 | 'type': 'location' 131 | } 132 | }], 133 | 'type': 'FeatureCollection' 134 | }, 135 | 'name': 'Lafayette', 136 | 'positions': [10], 137 | 'type': 'location' 138 | } 139 | }, { 140 | 'geometry': { 141 | 'coordinates': [-92.01984, 30.22409], 142 | 'type': 'Point' 143 | }, 144 | 'type': 'Feature', 145 | 'properties': { 146 | 'geoNameId': 4330145, 147 | 'alternateNames': ['لافایت، لوئیزیانا', 'лафейетт', 'la fa ye', 'lapies', 'لافیت، لوسیانہ', 'vermillion bridge', 'لافاييت', 'לפאייט', 'vermillionville', 'lafejet', 'lafayyt', 'village of saint jean au vermilion', 'lafajet', 'pinhool', '拉法葉', 'лафайет', 'лафејет', 'lafayette', 'ラファイエット', 'lafayt lwyyzyana', 'lafejett', '라피엣', 'vermilionville', 'rafaietto', 'little manchac', 'vermilion', 'la fayette', 'lft'], 148 | 'toponym': 'Lafayette', 149 | 'featureCode': 'PPLA2', 150 | 'countryCode': 'US', 151 | 'featureClass': 'P', 152 | 'hierarchy': { 153 | 'features': [{ 154 | 'geometry': null, 155 | 'type': 'Feature', 156 | 'properties': { 157 | 'geoNameId': 4330160, 158 | 'toponym': 'Lafayette Parish', 159 | 'hierarchy': { 160 | 'features': [], 161 | 'type': 'FeatureCollection' 162 | }, 163 | 'name': 'Lafayette', 164 | 'locationType': null, 165 | 'positions': [10], 166 | 'type': 'location' 167 | } 168 | }, { 169 | 'geometry': null, 170 | 'type': 'Feature', 171 | 'properties': { 172 | 'geoNameId': 4331987, 173 | 'toponym': 'Louisiana', 174 | 'hierarchy': { 175 | 'features': [], 176 | 'type': 'FeatureCollection' 177 | }, 178 | 'name': 'Lafayette', 179 | 'locationType': null, 180 | 'positions': [10], 181 | 'type': 'location' 182 | } 183 | }, { 184 | 'geometry': null, 185 | 'type': 'Feature', 186 | 'properties': { 187 | 'geoNameId': 6252001, 188 | 'toponym': 'United States', 189 | 'hierarchy': { 190 | 'features': [], 191 | 'type': 'FeatureCollection' 192 | }, 193 | 'name': 'Lafayette', 194 | 'locationType': null, 195 | 'positions': [10], 196 | 'type': 'location' 197 | } 198 | }, { 199 | 'geometry': null, 200 | 'type': 'Feature', 201 | 'properties': { 202 | 'geoNameId': 6255149, 203 | 'toponym': 'North America', 204 | 'hierarchy': { 205 | 'features': [], 206 | 'type': 'FeatureCollection' 207 | }, 208 | 'name': 'Lafayette', 209 | 'locationType': null, 210 | 'positions': [10], 211 | 'type': 'location' 212 | } 213 | }], 214 | 'type': 'FeatureCollection' 215 | }, 216 | 'name': 'Lafayette', 217 | 'locationType': null, 218 | 'positions': [10], 219 | 'type': 'location' 220 | } 221 | }, { 222 | 'geometry': { 223 | 'coordinates': [-85.2819, 34.7048], 224 | 'type': 'Point' 225 | }, 226 | 'type': 'Feature', 227 | 'properties': { 228 | 'geoNameId': 4204241, 229 | 'alternateNames': ['chattooga', 'лафејет', 'lafayette', 'la phayeta', 'ला फायेट', 'لافاييت', 'لافایت، جورجیا', 'benton', 'lafejet', 'lafayyt', 'lafayt jwrjya', 'لافائت، جارجیا', 'la fayette', 'lafayt jarjya'], 230 | 'toponym': 'LaFayette', 231 | 'featureCode': 'PPLA2', 232 | 'countryCode': 'US', 233 | 'featureClass': 'P', 234 | 'hierarchy': { 235 | 'features': [{ 236 | 'geometry': null, 237 | 'type': 'Feature', 238 | 'properties': { 239 | 'geoNameId': 4229163, 240 | 'toponym': 'Walker County', 241 | 'hierarchy': { 242 | 'features': [], 243 | 'type': 'FeatureCollection' 244 | }, 245 | 'name': 'Lafayette', 246 | 'locationType': null, 247 | 'positions': [10], 248 | 'type': 'location' 249 | } 250 | }, { 251 | 'geometry': null, 252 | 'type': 'Feature', 253 | 'properties': { 254 | 'geoNameId': 4197000, 255 | 'toponym': 'Georgia', 256 | 'hierarchy': { 257 | 'features': [], 258 | 'type': 'FeatureCollection' 259 | }, 260 | 'name': 'Lafayette', 261 | 'locationType': null, 262 | 'positions': [10], 263 | 'type': 'location' 264 | } 265 | }, { 266 | 'geometry': null, 267 | 'type': 'Feature', 268 | 'properties': { 269 | 'geoNameId': 6252001, 270 | 'toponym': 'United States', 271 | 'hierarchy': { 272 | 'features': [], 273 | 'type': 'FeatureCollection' 274 | }, 275 | 'name': 'Lafayette', 276 | 'locationType': null, 277 | 'positions': [10], 278 | 'type': 'location' 279 | } 280 | }, { 281 | 'geometry': null, 282 | 'type': 'Feature', 283 | 'properties': { 284 | 'geoNameId': 6255149, 285 | 'toponym': 'North America', 286 | 'hierarchy': { 287 | 'features': [], 288 | 'type': 'FeatureCollection' 289 | }, 290 | 'name': 'Lafayette', 291 | 'locationType': null, 292 | 'positions': [10], 293 | 'type': 'location' 294 | } 295 | }], 296 | 'type': 'FeatureCollection' 297 | }, 298 | 'name': 'Lafayette', 299 | 'locationType': null, 300 | 'positions': [10], 301 | 'type': 'location' 302 | } 303 | }, { 304 | 'geometry': { 305 | 'coordinates': [-86.02637, 36.52116], 306 | 'type': 'Point' 307 | }, 308 | 'type': 'Feature', 309 | 'properties': { 310 | 'geoNameId': 4635093, 311 | 'alternateNames': ['lfyt tnsy', 'لافاييت', 'лафејет', 'lafayette', 'lafejet', 'lafayyt', 'لافیت، ٹینیسی', 'la fayette', 'lafajet', 'لفیت، تنسی', 'лафайет'], 312 | 'toponym': 'Lafayette', 313 | 'featureCode': 'PPLA2', 314 | 'countryCode': 'US', 315 | 'featureClass': 'P', 316 | 'hierarchy': { 317 | 'features': [{ 318 | 'geometry': null, 319 | 'type': 'Feature', 320 | 'properties': { 321 | 'geoNameId': 4638991, 322 | 'toponym': 'Macon County', 323 | 'hierarchy': { 324 | 'features': [], 325 | 'type': 'FeatureCollection' 326 | }, 327 | 'name': 'Lafayette', 328 | 'locationType': null, 329 | 'positions': [10], 330 | 'type': 'location' 331 | } 332 | }, { 333 | 'geometry': null, 334 | 'type': 'Feature', 335 | 'properties': { 336 | 'geoNameId': 4662168, 337 | 'toponym': 'Tennessee', 338 | 'hierarchy': { 339 | 'features': [], 340 | 'type': 'FeatureCollection' 341 | }, 342 | 'name': 'Lafayette', 343 | 'locationType': null, 344 | 'positions': [10], 345 | 'type': 'location' 346 | } 347 | }, { 348 | 'geometry': null, 349 | 'type': 'Feature', 350 | 'properties': { 351 | 'geoNameId': 6252001, 352 | 'toponym': 'United States', 353 | 'hierarchy': { 354 | 'features': [], 355 | 'type': 'FeatureCollection' 356 | }, 357 | 'name': 'Lafayette', 358 | 'locationType': null, 359 | 'positions': [10], 360 | 'type': 'location' 361 | } 362 | }, { 363 | 'geometry': null, 364 | 'type': 'Feature', 365 | 'properties': { 366 | 'geoNameId': 6255149, 367 | 'toponym': 'North America', 368 | 'hierarchy': { 369 | 'features': [], 370 | 'type': 'FeatureCollection' 371 | }, 372 | 'name': 'Lafayette', 373 | 'locationType': null, 374 | 'positions': [10], 375 | 'type': 'location' 376 | } 377 | }], 378 | 'type': 'FeatureCollection' 379 | }, 380 | 'name': 'Lafayette', 381 | 'locationType': null, 382 | 'positions': [10], 383 | 'type': 'location' 384 | } 385 | }, { 386 | 'geometry': { 387 | 'coordinates': [-85.40106, 32.89985], 388 | 'type': 'Point' 389 | }, 390 | 'type': 'Feature', 391 | 'properties': { 392 | 'geoNameId': 4071267, 393 | 'alternateNames': ['lafayette', 'layfayette', 'la fayette', 'lafajet', 'chambersville', 'лафайет'], 394 | 'toponym': 'Lafayette', 395 | 'featureCode': 'PPLA2', 396 | 'countryCode': 'US', 397 | 'featureClass': 'P', 398 | 'hierarchy': { 399 | 'features': [{ 400 | 'geometry': null, 401 | 'type': 'Feature', 402 | 'properties': { 403 | 'geoNameId': 4054621, 404 | 'toponym': 'Chambers County', 405 | 'hierarchy': { 406 | 'features': [], 407 | 'type': 'FeatureCollection' 408 | }, 409 | 'name': 'Lafayette', 410 | 'locationType': null, 411 | 'positions': [10], 412 | 'type': 'location' 413 | } 414 | }, { 415 | 'geometry': null, 416 | 'type': 'Feature', 417 | 'properties': { 418 | 'geoNameId': 4829764, 419 | 'toponym': 'Alabama', 420 | 'hierarchy': { 421 | 'features': [], 422 | 'type': 'FeatureCollection' 423 | }, 424 | 'name': 'Lafayette', 425 | 'locationType': null, 426 | 'positions': [10], 427 | 'type': 'location' 428 | } 429 | }, { 430 | 'geometry': null, 431 | 'type': 'Feature', 432 | 'properties': { 433 | 'geoNameId': 6252001, 434 | 'toponym': 'United States', 435 | 'hierarchy': { 436 | 'features': [], 437 | 'type': 'FeatureCollection' 438 | }, 439 | 'name': 'Lafayette', 440 | 'locationType': null, 441 | 'positions': [10], 442 | 'type': 'location' 443 | } 444 | }, { 445 | 'geometry': null, 446 | 'type': 'Feature', 447 | 'properties': { 448 | 'geoNameId': 6255149, 449 | 'toponym': 'North America', 450 | 'hierarchy': { 451 | 'features': [], 452 | 'type': 'FeatureCollection' 453 | }, 454 | 'name': 'Lafayette', 455 | 'locationType': null, 456 | 'positions': [10], 457 | 'type': 'location' 458 | } 459 | }], 460 | 'type': 'FeatureCollection' 461 | }, 462 | 'name': 'Lafayette', 463 | 'locationType': null, 464 | 'positions': [10], 465 | 'type': 'location' 466 | } 467 | }, { 468 | 'geometry': { 469 | 'coordinates': [-85.89112, 42.21782], 470 | 'type': 'Point' 471 | }, 472 | 'type': 'Feature', 473 | 'properties': { 474 | 'geoNameId': 5005126, 475 | 'alternateNames': ['paw paw', 'lafayette'], 476 | 'toponym': 'Paw Paw', 477 | 'featureCode': 'PPLA2', 478 | 'countryCode': 'US', 479 | 'featureClass': 'P', 480 | 'hierarchy': { 481 | 'features': [{ 482 | 'geometry': null, 483 | 'type': 'Feature', 484 | 'properties': { 485 | 'geoNameId': 5013104, 486 | 'toponym': 'Van Buren County', 487 | 'hierarchy': { 488 | 'features': [], 489 | 'type': 'FeatureCollection' 490 | }, 491 | 'name': 'Lafayette', 492 | 'locationType': null, 493 | 'positions': [10], 494 | 'type': 'location' 495 | } 496 | }, { 497 | 'geometry': null, 498 | 'type': 'Feature', 499 | 'properties': { 500 | 'geoNameId': 5001836, 501 | 'toponym': 'Michigan', 502 | 'hierarchy': { 503 | 'features': [], 504 | 'type': 'FeatureCollection' 505 | }, 506 | 'name': 'Lafayette', 507 | 'locationType': null, 508 | 'positions': [10], 509 | 'type': 'location' 510 | } 511 | }, { 512 | 'geometry': null, 513 | 'type': 'Feature', 514 | 'properties': { 515 | 'geoNameId': 6252001, 516 | 'toponym': 'United States', 517 | 'hierarchy': { 518 | 'features': [], 519 | 'type': 'FeatureCollection' 520 | }, 521 | 'name': 'Lafayette', 522 | 'locationType': null, 523 | 'positions': [10], 524 | 'type': 'location' 525 | } 526 | }, { 527 | 'geometry': null, 528 | 'type': 'Feature', 529 | 'properties': { 530 | 'geoNameId': 6255149, 531 | 'toponym': 'North America', 532 | 'hierarchy': { 533 | 'features': [], 534 | 'type': 'FeatureCollection' 535 | }, 536 | 'name': 'Lafayette', 537 | 'locationType': null, 538 | 'positions': [10], 539 | 'type': 'location' 540 | } 541 | }], 542 | 'type': 'FeatureCollection' 543 | }, 544 | 'name': 'Lafayette', 545 | 'locationType': null, 546 | 'positions': [10], 547 | 'type': 'location' 548 | } 549 | }, { 550 | 'geometry': { 551 | 'coordinates': [-93.57768, 33.35846], 552 | 'type': 'Point' 553 | }, 554 | 'type': 'Feature', 555 | 'properties': { 556 | 'geoNameId': 4118755, 557 | 'alternateNames': ['new lewisville', 'луисвил', 'luisvil', 'lwysfyl', 'लिविसभिल', 'l\'juisvil\'', 'lafayette court house', 'lewisville', 'لويسفيل', 'льюисвилл', 'لوئیس‌ویل', 'livisabhila', 'l\'juisvill', 'льюїсвіль'], 558 | 'toponym': 'Lewisville', 559 | 'featureCode': 'PPLA2', 560 | 'countryCode': 'US', 561 | 'featureClass': 'P', 562 | 'hierarchy': { 563 | 'features': [{ 564 | 'geometry': null, 565 | 'type': 'Feature', 566 | 'properties': { 567 | 'geoNameId': 4117774, 568 | 'toponym': 'Lafayette County', 569 | 'hierarchy': { 570 | 'features': [], 571 | 'type': 'FeatureCollection' 572 | }, 573 | 'name': 'Lafayette', 574 | 'locationType': null, 575 | 'positions': [10], 576 | 'type': 'location' 577 | } 578 | }, { 579 | 'geometry': null, 580 | 'type': 'Feature', 581 | 'properties': { 582 | 'geoNameId': 4099753, 583 | 'toponym': 'Arkansas', 584 | 'hierarchy': { 585 | 'features': [], 586 | 'type': 'FeatureCollection' 587 | }, 588 | 'name': 'Lafayette', 589 | 'locationType': null, 590 | 'positions': [10], 591 | 'type': 'location' 592 | } 593 | }, { 594 | 'geometry': null, 595 | 'type': 'Feature', 596 | 'properties': { 597 | 'geoNameId': 6252001, 598 | 'toponym': 'United States', 599 | 'hierarchy': { 600 | 'features': [], 601 | 'type': 'FeatureCollection' 602 | }, 603 | 'name': 'Lafayette', 604 | 'locationType': null, 605 | 'positions': [10], 606 | 'type': 'location' 607 | } 608 | }, { 609 | 'geometry': null, 610 | 'type': 'Feature', 611 | 'properties': { 612 | 'geoNameId': 6255149, 613 | 'toponym': 'North America', 614 | 'hierarchy': { 615 | 'features': [], 616 | 'type': 'FeatureCollection' 617 | }, 618 | 'name': 'Lafayette', 619 | 'locationType': null, 620 | 'positions': [10], 621 | 'type': 'location' 622 | } 623 | }], 624 | 'type': 'FeatureCollection' 625 | }, 626 | 'name': 'Lafayette', 627 | 'locationType': null, 628 | 'positions': [10], 629 | 'type': 'location' 630 | } 631 | }, { 632 | 'geometry': { 633 | 'coordinates': [-87.83085, 33.68455], 634 | 'type': 'Point' 635 | }, 636 | 'type': 'Feature', 637 | 'properties': { 638 | 'geoNameId': 4061586, 639 | 'alternateNames': ['faett', 'فاييت', 'lafayette', 'фејет', 'fayette', 'layfayette', 'fayette depot', 'فایت، آلاباما', 'fayyt', 'fei ye te', '페이엣', 'frog level', 'fejet', 'fayt alabama', 'fayette courthouse', 'latona', 'peies', 'фаєтт', 'fayette depot town', 'fayetteville', 'فایێت، ئەلاباما', '费耶特', 'latone'], 640 | 'toponym': 'Fayette', 641 | 'featureCode': 'PPLA2', 642 | 'countryCode': 'US', 643 | 'featureClass': 'P', 644 | 'hierarchy': { 645 | 'features': [{ 646 | 'geometry': null, 647 | 'type': 'Feature', 648 | 'properties': { 649 | 'geoNameId': 4061590, 650 | 'toponym': 'Fayette County', 651 | 'hierarchy': { 652 | 'features': [], 653 | 'type': 'FeatureCollection' 654 | }, 655 | 'name': 'Lafayette', 656 | 'locationType': null, 657 | 'positions': [10], 658 | 'type': 'location' 659 | } 660 | }, { 661 | 'geometry': null, 662 | 'type': 'Feature', 663 | 'properties': { 664 | 'geoNameId': 4829764, 665 | 'toponym': 'Alabama', 666 | 'hierarchy': { 667 | 'features': [], 668 | 'type': 'FeatureCollection' 669 | }, 670 | 'name': 'Lafayette', 671 | 'locationType': null, 672 | 'positions': [10], 673 | 'type': 'location' 674 | } 675 | }, { 676 | 'geometry': null, 677 | 'type': 'Feature', 678 | 'properties': { 679 | 'geoNameId': 6252001, 680 | 'toponym': 'United States', 681 | 'hierarchy': { 682 | 'features': [], 683 | 'type': 'FeatureCollection' 684 | }, 685 | 'name': 'Lafayette', 686 | 'locationType': null, 687 | 'positions': [10], 688 | 'type': 'location' 689 | } 690 | }, { 691 | 'geometry': null, 692 | 'type': 'Feature', 693 | 'properties': { 694 | 'geoNameId': 6255149, 695 | 'toponym': 'North America', 696 | 'hierarchy': { 697 | 'features': [], 698 | 'type': 'FeatureCollection' 699 | }, 700 | 'name': 'Lafayette', 701 | 'locationType': null, 702 | 'positions': [10], 703 | 'type': 'location' 704 | } 705 | }], 706 | 'type': 'FeatureCollection' 707 | }, 708 | 'name': 'Lafayette', 709 | 'locationType': null, 710 | 'positions': [10], 711 | 'type': 'location' 712 | } 713 | }, { 714 | 'geometry': { 715 | 'coordinates': [-105.08971, 39.9936], 716 | 'type': 'Point' 717 | }, 718 | 'type': 'Feature', 719 | 'properties': { 720 | 'geoNameId': 5427771, 721 | 'alternateNames': ['лафејет', 'lafayette', 'la fa ye', 'лафайетт', 'lafayt klradw', 'لافائیٹ، کولوراڈو', '라피엣', 'lapies', '拉法叶', 'لافاييت', 'لافایت، کلرادو', 'lafejet', 'lafayyt', 'लाफायेट', 'lafajett', 'lafajet', 'лафайет', 'laphayeta'], 722 | 'toponym': 'Lafayette', 723 | 'featureCode': 'PPL', 724 | 'countryCode': 'US', 725 | 'featureClass': 'P', 726 | 'hierarchy': { 727 | 'features': [{ 728 | 'geometry': null, 729 | 'type': 'Feature', 730 | 'properties': { 731 | 'geoNameId': 5574999, 732 | 'toponym': 'Boulder County', 733 | 'hierarchy': { 734 | 'features': [], 735 | 'type': 'FeatureCollection' 736 | }, 737 | 'name': 'Lafayette', 738 | 'locationType': null, 739 | 'positions': [10], 740 | 'type': 'location' 741 | } 742 | }, { 743 | 'geometry': null, 744 | 'type': 'Feature', 745 | 'properties': { 746 | 'geoNameId': 5417618, 747 | 'toponym': 'Colorado', 748 | 'hierarchy': { 749 | 'features': [], 750 | 'type': 'FeatureCollection' 751 | }, 752 | 'name': 'Lafayette', 753 | 'locationType': null, 754 | 'positions': [10], 755 | 'type': 'location' 756 | } 757 | }, { 758 | 'geometry': null, 759 | 'type': 'Feature', 760 | 'properties': { 761 | 'geoNameId': 6252001, 762 | 'toponym': 'United States', 763 | 'hierarchy': { 764 | 'features': [], 765 | 'type': 'FeatureCollection' 766 | }, 767 | 'name': 'Lafayette', 768 | 'locationType': null, 769 | 'positions': [10], 770 | 'type': 'location' 771 | } 772 | }, { 773 | 'geometry': null, 774 | 'type': 'Feature', 775 | 'properties': { 776 | 'geoNameId': 6255149, 777 | 'toponym': 'North America', 778 | 'hierarchy': { 779 | 'features': [], 780 | 'type': 'FeatureCollection' 781 | }, 782 | 'name': 'Lafayette', 783 | 'locationType': null, 784 | 'positions': [10], 785 | 'type': 'location' 786 | } 787 | }], 788 | 'type': 'FeatureCollection' 789 | }, 790 | 'name': 'Lafayette', 791 | 'locationType': null, 792 | 'positions': [10], 793 | 'type': 'location' 794 | } 795 | }, { 796 | 'geometry': { 797 | 'coordinates': [-122.11802, 37.88576], 798 | 'type': 'Point' 799 | }, 800 | 'type': 'Feature', 801 | 'properties': { 802 | 'geoNameId': 5364226, 803 | 'alternateNames': ['лафејет', 'lafayette', 'ラファイエット', '라피엣', 'lapies', 'lafayt kalyfrnya', 'lafayyty', 'rafaietto', '拉斐特', 'لافاييتي', 'la fei te', 'lafejet', 'लाफायेट', 'la fayette', 'لافایت، کالیفرنیا', 'lafajet', 'лафайет', 'laphayeta'], 804 | 'toponym': 'Lafayette', 805 | 'featureCode': 'PPL', 806 | 'countryCode': 'US', 807 | 'featureClass': 'P', 808 | 'hierarchy': { 809 | 'features': [{ 810 | 'geometry': null, 811 | 'type': 'Feature', 812 | 'properties': { 813 | 'geoNameId': 5339268, 814 | 'toponym': 'Contra Costa County', 815 | 'hierarchy': { 816 | 'features': [], 817 | 'type': 'FeatureCollection' 818 | }, 819 | 'name': 'Lafayette', 820 | 'locationType': null, 821 | 'positions': [10], 822 | 'type': 'location' 823 | } 824 | }, { 825 | 'geometry': null, 826 | 'type': 'Feature', 827 | 'properties': { 828 | 'geoNameId': 5332921, 829 | 'toponym': 'California', 830 | 'hierarchy': { 831 | 'features': [], 832 | 'type': 'FeatureCollection' 833 | }, 834 | 'name': 'Lafayette', 835 | 'locationType': null, 836 | 'positions': [10], 837 | 'type': 'location' 838 | } 839 | }, { 840 | 'geometry': null, 841 | 'type': 'Feature', 842 | 'properties': { 843 | 'geoNameId': 6252001, 844 | 'toponym': 'United States', 845 | 'hierarchy': { 846 | 'features': [], 847 | 'type': 'FeatureCollection' 848 | }, 849 | 'name': 'Lafayette', 850 | 'locationType': null, 851 | 'positions': [10], 852 | 'type': 'location' 853 | } 854 | }, { 855 | 'geometry': null, 856 | 'type': 'Feature', 857 | 'properties': { 858 | 'geoNameId': 6255149, 859 | 'toponym': 'North America', 860 | 'hierarchy': { 861 | 'features': [], 862 | 'type': 'FeatureCollection' 863 | }, 864 | 'name': 'Lafayette', 865 | 'locationType': null, 866 | 'positions': [10], 867 | 'type': 'location' 868 | } 869 | }], 870 | 'type': 'FeatureCollection' 871 | }, 872 | 'name': 'Lafayette', 873 | 'locationType': null, 874 | 'positions': [10], 875 | 'type': 'location' 876 | } 877 | }, { 878 | 'geometry': { 879 | 'coordinates': [-123.11483, 45.24428], 880 | 'type': 'Point' 881 | }, 882 | 'type': 'Feature', 883 | 'properties': { 884 | 'geoNameId': 5735609, 885 | 'alternateNames': ['лафејет', 'لافایت، اورگن', 'lafayette', 'lafayt awrgn', 'لافیت، اوریگان', 'lafajehtt', 'лафайэтт', 'لافاييت', 'lafejet', 'lafayyt', 'lafyt awrygan', 'lafajet', 'лафайет'], 886 | 'toponym': 'Lafayette', 887 | 'featureCode': 'PPL', 888 | 'countryCode': 'US', 889 | 'featureClass': 'P', 890 | 'hierarchy': { 891 | 'features': [{ 892 | 'geometry': null, 893 | 'type': 'Feature', 894 | 'properties': { 895 | 'geoNameId': 5761960, 896 | 'toponym': 'Yamhill County', 897 | 'hierarchy': { 898 | 'features': [], 899 | 'type': 'FeatureCollection' 900 | }, 901 | 'name': 'Lafayette', 902 | 'locationType': null, 903 | 'positions': [10], 904 | 'type': 'location' 905 | } 906 | }, { 907 | 'geometry': null, 908 | 'type': 'Feature', 909 | 'properties': { 910 | 'geoNameId': 5744337, 911 | 'toponym': 'Oregon', 912 | 'hierarchy': { 913 | 'features': [], 914 | 'type': 'FeatureCollection' 915 | }, 916 | 'name': 'Lafayette', 917 | 'locationType': null, 918 | 'positions': [10], 919 | 'type': 'location' 920 | } 921 | }, { 922 | 'geometry': null, 923 | 'type': 'Feature', 924 | 'properties': { 925 | 'geoNameId': 6252001, 926 | 'toponym': 'United States', 927 | 'hierarchy': { 928 | 'features': [], 929 | 'type': 'FeatureCollection' 930 | }, 931 | 'name': 'Lafayette', 932 | 'locationType': null, 933 | 'positions': [10], 934 | 'type': 'location' 935 | } 936 | }, { 937 | 'geometry': null, 938 | 'type': 'Feature', 939 | 'properties': { 940 | 'geoNameId': 6255149, 941 | 'toponym': 'North America', 942 | 'hierarchy': { 943 | 'features': [], 944 | 'type': 'FeatureCollection' 945 | }, 946 | 'name': 'Lafayette', 947 | 'locationType': null, 948 | 'positions': [10], 949 | 'type': 'location' 950 | } 951 | }], 952 | 'type': 'FeatureCollection' 953 | }, 954 | 'name': 'Lafayette', 955 | 'locationType': null, 956 | 'positions': [10], 957 | 'type': 'location' 958 | } 959 | }, { 960 | 'geometry': { 961 | 'coordinates': [-85.71609, 36.18589], 962 | 'type': 'Point' 963 | }, 964 | 'type': 'Feature', 965 | 'properties': { 966 | 'geoNameId': 4635094, 967 | 'alternateNames': ['lafayette'], 968 | 'toponym': 'Lafayette', 969 | 'featureCode': 'PPL', 970 | 'countryCode': 'US', 971 | 'featureClass': 'P', 972 | 'hierarchy': { 973 | 'features': [{ 974 | 'geometry': null, 975 | 'type': 'Feature', 976 | 'properties': { 977 | 'geoNameId': 4651744, 978 | 'toponym': 'Putnam County', 979 | 'hierarchy': { 980 | 'features': [], 981 | 'type': 'FeatureCollection' 982 | }, 983 | 'name': 'Lafayette', 984 | 'locationType': null, 985 | 'positions': [10], 986 | 'type': 'location' 987 | } 988 | }, { 989 | 'geometry': null, 990 | 'type': 'Feature', 991 | 'properties': { 992 | 'geoNameId': 4662168, 993 | 'toponym': 'Tennessee', 994 | 'hierarchy': { 995 | 'features': [], 996 | 'type': 'FeatureCollection' 997 | }, 998 | 'name': 'Lafayette', 999 | 'locationType': null, 1000 | 'positions': [10], 1001 | 'type': 'location' 1002 | } 1003 | }, { 1004 | 'geometry': null, 1005 | 'type': 'Feature', 1006 | 'properties': { 1007 | 'geoNameId': 6252001, 1008 | 'toponym': 'United States', 1009 | 'hierarchy': { 1010 | 'features': [], 1011 | 'type': 'FeatureCollection' 1012 | }, 1013 | 'name': 'Lafayette', 1014 | 'locationType': null, 1015 | 'positions': [10], 1016 | 'type': 'location' 1017 | } 1018 | }, { 1019 | 'geometry': null, 1020 | 'type': 'Feature', 1021 | 'properties': { 1022 | 'geoNameId': 6255149, 1023 | 'toponym': 'North America', 1024 | 'hierarchy': { 1025 | 'features': [], 1026 | 'type': 'FeatureCollection' 1027 | }, 1028 | 'name': 'Lafayette', 1029 | 'locationType': null, 1030 | 'positions': [10], 1031 | 'type': 'location' 1032 | } 1033 | }], 1034 | 'type': 'FeatureCollection' 1035 | }, 1036 | 'name': 'Lafayette', 1037 | 'locationType': null, 1038 | 'positions': [10], 1039 | 'type': 'location' 1040 | } 1041 | }, { 1042 | 'geometry': { 1043 | 'coordinates': [-83.94855, 40.76033], 1044 | 'type': 'Point' 1045 | }, 1046 | 'type': 'Feature', 1047 | 'properties': { 1048 | 'geoNameId': 5160028, 1049 | 'alternateNames': ['lafayette', 'herring', 'la fayette', 'lafajet', 'лафайет'], 1050 | 'toponym': 'Lafayette', 1051 | 'featureCode': 'PPL', 1052 | 'countryCode': 'US', 1053 | 'featureClass': 'P', 1054 | 'hierarchy': { 1055 | 'features': [{ 1056 | 'geometry': null, 1057 | 'type': 'Feature', 1058 | 'properties': { 1059 | 'geoNameId': 5145576, 1060 | 'toponym': 'Allen County', 1061 | 'hierarchy': { 1062 | 'features': [], 1063 | 'type': 'FeatureCollection' 1064 | }, 1065 | 'name': 'Lafayette', 1066 | 'locationType': null, 1067 | 'positions': [10], 1068 | 'type': 'location' 1069 | } 1070 | }, { 1071 | 'geometry': null, 1072 | 'type': 'Feature', 1073 | 'properties': { 1074 | 'geoNameId': 5165418, 1075 | 'toponym': 'Ohio', 1076 | 'hierarchy': { 1077 | 'features': [], 1078 | 'type': 'FeatureCollection' 1079 | }, 1080 | 'name': 'Lafayette', 1081 | 'locationType': null, 1082 | 'positions': [10], 1083 | 'type': 'location' 1084 | } 1085 | }, { 1086 | 'geometry': null, 1087 | 'type': 'Feature', 1088 | 'properties': { 1089 | 'geoNameId': 6252001, 1090 | 'toponym': 'United States', 1091 | 'hierarchy': { 1092 | 'features': [], 1093 | 'type': 'FeatureCollection' 1094 | }, 1095 | 'name': 'Lafayette', 1096 | 'locationType': null, 1097 | 'positions': [10], 1098 | 'type': 'location' 1099 | } 1100 | }, { 1101 | 'geometry': null, 1102 | 'type': 'Feature', 1103 | 'properties': { 1104 | 'geoNameId': 6255149, 1105 | 'toponym': 'North America', 1106 | 'hierarchy': { 1107 | 'features': [], 1108 | 'type': 'FeatureCollection' 1109 | }, 1110 | 'name': 'Lafayette', 1111 | 'locationType': null, 1112 | 'positions': [10], 1113 | 'type': 'location' 1114 | } 1115 | }], 1116 | 'type': 'FeatureCollection' 1117 | }, 1118 | 'name': 'Lafayette', 1119 | 'locationType': null, 1120 | 'positions': [10], 1121 | 'type': 'location' 1122 | } 1123 | }, { 1124 | 'geometry': { 1125 | 'coordinates': [-94.39525, 44.44663], 1126 | 'type': 'Point' 1127 | }, 1128 | 'type': 'Feature', 1129 | 'properties': { 1130 | 'geoNameId': 5033667, 1131 | 'alternateNames': ['city of lafayette', 'lafayette', 'lafajet', 'лафайет'], 1132 | 'toponym': 'Lafayette', 1133 | 'featureCode': 'PPL', 1134 | 'countryCode': 'US', 1135 | 'featureClass': 'P', 1136 | 'hierarchy': { 1137 | 'features': [{ 1138 | 'geometry': null, 1139 | 'type': 'Feature', 1140 | 'properties': { 1141 | 'geoNameId': 5039249, 1142 | 'toponym': 'Nicollet County', 1143 | 'hierarchy': { 1144 | 'features': [], 1145 | 'type': 'FeatureCollection' 1146 | }, 1147 | 'name': 'Lafayette', 1148 | 'locationType': null, 1149 | 'positions': [10], 1150 | 'type': 'location' 1151 | } 1152 | }, { 1153 | 'geometry': null, 1154 | 'type': 'Feature', 1155 | 'properties': { 1156 | 'geoNameId': 5037779, 1157 | 'toponym': 'Minnesota', 1158 | 'hierarchy': { 1159 | 'features': [], 1160 | 'type': 'FeatureCollection' 1161 | }, 1162 | 'name': 'Lafayette', 1163 | 'locationType': null, 1164 | 'positions': [10], 1165 | 'type': 'location' 1166 | } 1167 | }, { 1168 | 'geometry': null, 1169 | 'type': 'Feature', 1170 | 'properties': { 1171 | 'geoNameId': 6252001, 1172 | 'toponym': 'United States', 1173 | 'hierarchy': { 1174 | 'features': [], 1175 | 'type': 'FeatureCollection' 1176 | }, 1177 | 'name': 'Lafayette', 1178 | 'locationType': null, 1179 | 'positions': [10], 1180 | 'type': 'location' 1181 | } 1182 | }, { 1183 | 'geometry': null, 1184 | 'type': 'Feature', 1185 | 'properties': { 1186 | 'geoNameId': 6255149, 1187 | 'toponym': 'North America', 1188 | 'hierarchy': { 1189 | 'features': [], 1190 | 'type': 'FeatureCollection' 1191 | }, 1192 | 'name': 'Lafayette', 1193 | 'locationType': null, 1194 | 'positions': [10], 1195 | 'type': 'location' 1196 | } 1197 | }], 1198 | 'type': 'FeatureCollection' 1199 | }, 1200 | 'name': 'Lafayette', 1201 | 'locationType': null, 1202 | 'positions': [10], 1203 | 'type': 'location' 1204 | } 1205 | }, { 1206 | 'geometry': { 1207 | 'coordinates': [-83.40659, 39.93756], 1208 | 'type': 'Point' 1209 | }, 1210 | 'type': 'Feature', 1211 | 'properties': { 1212 | 'geoNameId': 4516099, 1213 | 'alternateNames': ['lafayette', 'fayette', 'limerick', 'la fayette', 'lawrenceville'], 1214 | 'toponym': 'Lafayette', 1215 | 'featureCode': 'PPL', 1216 | 'countryCode': 'US', 1217 | 'featureClass': 'P', 1218 | 'hierarchy': { 1219 | 'features': [{ 1220 | 'geometry': null, 1221 | 'type': 'Feature', 1222 | 'properties': { 1223 | 'geoNameId': 4517365, 1224 | 'toponym': 'Madison County', 1225 | 'hierarchy': { 1226 | 'features': [], 1227 | 'type': 'FeatureCollection' 1228 | }, 1229 | 'name': 'Lafayette', 1230 | 'locationType': null, 1231 | 'positions': [10], 1232 | 'type': 'location' 1233 | } 1234 | }, { 1235 | 'geometry': null, 1236 | 'type': 'Feature', 1237 | 'properties': { 1238 | 'geoNameId': 5165418, 1239 | 'toponym': 'Ohio', 1240 | 'hierarchy': { 1241 | 'features': [], 1242 | 'type': 'FeatureCollection' 1243 | }, 1244 | 'name': 'Lafayette', 1245 | 'locationType': null, 1246 | 'positions': [10], 1247 | 'type': 'location' 1248 | } 1249 | }, { 1250 | 'geometry': null, 1251 | 'type': 'Feature', 1252 | 'properties': { 1253 | 'geoNameId': 6252001, 1254 | 'toponym': 'United States', 1255 | 'hierarchy': { 1256 | 'features': [], 1257 | 'type': 'FeatureCollection' 1258 | }, 1259 | 'name': 'Lafayette', 1260 | 'locationType': null, 1261 | 'positions': [10], 1262 | 'type': 'location' 1263 | } 1264 | }, { 1265 | 'geometry': null, 1266 | 'type': 'Feature', 1267 | 'properties': { 1268 | 'geoNameId': 6255149, 1269 | 'toponym': 'North America', 1270 | 'hierarchy': { 1271 | 'features': [], 1272 | 'type': 'FeatureCollection' 1273 | }, 1274 | 'name': 'Lafayette', 1275 | 'locationType': null, 1276 | 'positions': [10], 1277 | 'type': 'location' 1278 | } 1279 | }], 1280 | 'type': 'FeatureCollection' 1281 | }, 1282 | 'name': 'Lafayette', 1283 | 'locationType': null, 1284 | 'positions': [10], 1285 | 'type': 'location' 1286 | } 1287 | }], 1288 | 'type': 'FeatureCollection' 1289 | } 1290 | } 1291 | }, { 1292 | 'geometry': { 1293 | 'coordinates': [-86.25027, 40.00032], 1294 | 'type': 'Point' 1295 | }, 1296 | 'type': 'Feature', 1297 | 'properties': { 1298 | 'geoNameId': 4921868, 1299 | 'alternateNames': null, 1300 | 'toponym': 'Indiana', 1301 | 'featureCode': 'ADM1', 1302 | 'countryCode': 'US', 1303 | 'featureClass': 'A', 1304 | 'hierarchy': { 1305 | 'features': [{ 1306 | 'geometry': null, 1307 | 'type': 'Feature', 1308 | 'properties': { 1309 | 'geoNameId': 6252001, 1310 | 'toponym': 'United States', 1311 | 'hierarchy': null, 1312 | 'name': 'Indiana', 1313 | 'locationType': null, 1314 | 'positions': [21], 1315 | 'type': 'location' 1316 | } 1317 | }, { 1318 | 'geometry': null, 1319 | 'type': 'Feature', 1320 | 'properties': { 1321 | 'geoNameId': 6255149, 1322 | 'toponym': 'North America', 1323 | 'hierarchy': null, 1324 | 'name': 'Indiana', 1325 | 'locationType': null, 1326 | 'positions': [21], 1327 | 'type': 'location' 1328 | } 1329 | }], 1330 | 'type': 'FeatureCollection' 1331 | }, 1332 | 'name': 'Indiana', 1333 | 'positions': [21], 1334 | 'type': 'location', 1335 | 'alternates': { 1336 | 'features': [{ 1337 | 'geometry': { 1338 | 'coordinates': [-86.25027, 40.00032], 1339 | 'type': 'Point' 1340 | }, 1341 | 'type': 'Feature', 1342 | 'properties': { 1343 | 'geoNameId': 4921868, 1344 | 'toponym': 'Indiana', 1345 | 'featureCode': 'ADM1', 1346 | 'countryCode': 'US', 1347 | 'featureClass': 'A', 1348 | 'hierarchy': { 1349 | 'features': [{ 1350 | 'geometry': null, 1351 | 'type': 'Feature', 1352 | 'properties': { 1353 | 'geoNameId': 6252001, 1354 | 'toponym': 'United States', 1355 | 'hierarchy': null, 1356 | 'name': 'Indiana', 1357 | 'locationType': null, 1358 | 'positions': [21], 1359 | 'type': 'location' 1360 | } 1361 | }, { 1362 | 'geometry': null, 1363 | 'type': 'Feature', 1364 | 'properties': { 1365 | 'geoNameId': 6255149, 1366 | 'toponym': 'North America', 1367 | 'hierarchy': null, 1368 | 'name': 'Indiana', 1369 | 'locationType': null, 1370 | 'positions': [21], 1371 | 'type': 'location' 1372 | } 1373 | }], 1374 | 'type': 'FeatureCollection' 1375 | }, 1376 | 'name': 'Indiana', 1377 | 'positions': [21], 1378 | 'type': 'location' 1379 | } 1380 | }, { 1381 | 'geometry': { 1382 | 'coordinates': [-79.15253, 40.62146], 1383 | 'type': 'Point' 1384 | }, 1385 | 'type': 'Feature', 1386 | 'properties': { 1387 | 'geoNameId': 5194868, 1388 | 'alternateNames': ['индиана', 'indijana', 'ayndyana pnsylwanya', 'ایندیانا، پنسیلوانیا', 'インディアナ', 'индијана', 'idi', 'indiana'], 1389 | 'toponym': 'Indiana', 1390 | 'featureCode': 'PPLA2', 1391 | 'countryCode': 'US', 1392 | 'featureClass': 'P', 1393 | 'hierarchy': { 1394 | 'features': [{ 1395 | 'geometry': null, 1396 | 'type': 'Feature', 1397 | 'properties': { 1398 | 'geoNameId': 5194872, 1399 | 'toponym': 'Indiana County', 1400 | 'hierarchy': { 1401 | 'features': [], 1402 | 'type': 'FeatureCollection' 1403 | }, 1404 | 'name': 'Indiana', 1405 | 'locationType': null, 1406 | 'positions': [21], 1407 | 'type': 'location' 1408 | } 1409 | }, { 1410 | 'geometry': null, 1411 | 'type': 'Feature', 1412 | 'properties': { 1413 | 'geoNameId': 6254927, 1414 | 'toponym': 'Pennsylvania', 1415 | 'hierarchy': { 1416 | 'features': [], 1417 | 'type': 'FeatureCollection' 1418 | }, 1419 | 'name': 'Indiana', 1420 | 'locationType': null, 1421 | 'positions': [21], 1422 | 'type': 'location' 1423 | } 1424 | }, { 1425 | 'geometry': null, 1426 | 'type': 'Feature', 1427 | 'properties': { 1428 | 'geoNameId': 6252001, 1429 | 'toponym': 'United States', 1430 | 'hierarchy': { 1431 | 'features': [], 1432 | 'type': 'FeatureCollection' 1433 | }, 1434 | 'name': 'Indiana', 1435 | 'locationType': null, 1436 | 'positions': [21], 1437 | 'type': 'location' 1438 | } 1439 | }, { 1440 | 'geometry': null, 1441 | 'type': 'Feature', 1442 | 'properties': { 1443 | 'geoNameId': 6255149, 1444 | 'toponym': 'North America', 1445 | 'hierarchy': { 1446 | 'features': [], 1447 | 'type': 'FeatureCollection' 1448 | }, 1449 | 'name': 'Indiana', 1450 | 'locationType': null, 1451 | 'positions': [21], 1452 | 'type': 'location' 1453 | } 1454 | }], 1455 | 'type': 'FeatureCollection' 1456 | }, 1457 | 'name': 'Indiana', 1458 | 'locationType': null, 1459 | 'positions': [21], 1460 | 'type': 'location' 1461 | } 1462 | }, { 1463 | 'geometry': { 1464 | 'coordinates': [72, -6], 1465 | 'type': 'Point' 1466 | }, 1467 | 'type': 'Feature', 1468 | 'properties': { 1469 | 'geoNameId': 1282588, 1470 | 'alternateNames': ['territoires britanniques de l\'océan indien', 'i-british indian ocean territory', 'britske indickooceanske uzemie', 'britich xin deiy n xo cheiy n ther ri thx ri', 'британско индиско океанска територија', 'britisches territorium im indischen ozean', 'britu indijas okeāna teritorija', 'ဗြိတိသျှ အိန္ဒိယသမုဒ္ဒရာ နယ်မြေ', 'britske uzemie v indickom oceane', 'teritwari y\'inyanja y\'abahinde nyongereza', 'territorio oceanic britanno-indian', 'brytyjskie terytorium oceanu indyjskiego', 'brits indische oceaanterritorium', 'lãnh thổ ấn độ dương thuộc anh', 'indijas okeana britu teritorija', 'ब्रिटिश इंडियन ओशन टेरीटरी', 'பிரிட்டிஷ் இந்தியப் பெருங்கடல் பகுதி', 'indi okeanyndagy britan territorijasy', 'intara y\'ubwongereza yo mu birwa by\'abahindi', 'บริติชอินเดียนโอเชียนเทร์ริทอรี', 'teritöio britannego de loçeano indian', 'indiako ozeanoko britainiar lurraldea', 'tiriogaeth cefnfor india prydain', 'teritori samudra hindia britania', 'indijy furdy britanijy territori', 'የብሪታንያ ሕንድ ውቅያኖስ ግዛት', 'britaniana teritorio en indiana oceano', 'مستعمره‌های بریتانیا در اقیانوس هند', 'teritoriul britanic din oceanul indian', 'keeriindi britaani to maayo enndo', 'territorio britanico do oceano indico', 'territorio britannico dell’oceano indiano', 'britiske territorier i indiahavet', 'orílẹ́ède etíkun índíánì ti ìlú bírítísì', 'brittiläinen intian valtameren alue', 'det britiske territoriet i indiahavet', 'territorio britannico delloceano indiano', 'ბრიტანეთის ინდოეთის ოკეანის ტერიტორია', 'yeong-guglyeong indoyang jiyeog', 'ಬ್ರಿಟೀಶ್ ಇಂಡಿಯನ್ ಮಹಾಸಾಗರ ಪ್ರದೇಶ', 'ingiliz hint okyanusu boelgesi', 'potu fonua moana \'initia fakapilitania', 'faridranomasina indiana britanika', 'territorio británico del océano índico', 'tiriad breizhveurat meurvor indez', 'thuoc gia anh tai an gjo duong', 'britaniya-hindistan okeanik territoriyası', 'territori britanic de l\'ocea indic', 'territorio britanico del oceano indico', 'ब्रिटिश हिंद महासागर क्षेत्र', 'брытанская тэрыторыя ў індыйскім акіяне', 'βρετανικό έδαφος ινδικού ωκεανού', 'የብሪታኒያ ህንድ ውቂያኖስ ግዛት', 'britisa indiyana osana teritari', 'brita hindoceana teritorio', '英領インド洋地域', 'ying shu yin du yang ling de', 'aqlym almhyt alhndy albrytany', 'बेलायती हिन्द महासागर क्षेत्र', 'bretsku indiahavsoyggjarnar', 'britans\'ka teritorija v indijs\'komu okeani', 'téritori samudra hindia britania', 'britisa hinda mahasagariya ksetra', 'britanin latta indin okeanekh\'', 'territori britannic en l\'ocean indic', 'britiske omrade i det indiske hav', 'brit indiai-óceáni terület', 'βρετανικά εδάφη ινδικού ωκεανού', 'territoire britannique de l\'océan indien', 'britse gebieden in de indische oceaan', 'indijos vandenyno britu sritis', 'brittiska territoriet i indiska oceanen', 'قلمرو بریتانیا در اقیانوس هند', 'agbègbè òkun índíà brítánì', 'britansko ozemlje v indijskem oceanu', 'eneo la uingereza katika bahari hindi', 'britse indiese oseaan gebied', 'pirittaniya intiyap perunkatal mantalam', 'igirisu lingindo yang de yu', 'territoire britannique de l\'ocean indien', 'territorio británico do océano índico', 'イギリス領インド洋地域', 'հնդկական օվկիանոսի բրիտանական տարածքներ', '英属印度洋领地', 'британская территория в индийском океане', 'teritwari y’inyanja y’abahinde nyongereza', 'britisa bharata mahasagariya ancala', 'vretanika edafi indikou okeanou', 'britisa hindi mahasagara ksetra', 'britis bharatiya osan prantam', 'британин латта индин океанехь', 'ব্ৰিটিশ্ব ইণ্ডিয়ান মহাসাগৰৰ অঞ্চল', 'брытанская тэрыторыя індыйскага акіяну', 'britansko indisko okeanska teritorija', 'indijas okeāna britu teritorija', 'ব্রিটিশ ভারত মহাসাগরীয় অঞ্চল', 'brytanskaa terytorya indyjskaga akianu', 'britanske indijskookeanske teritorije', 'инди океанындагы британ территориясы', 'britanski teritorii v indijskija okean', 'territorio britanico de locian indico', 'הטריטוריה הבריטית באוקיינוס ההודי', 'britanya hint okyanusu toprakları', 'alaqlym albrytany fy almhyt alhndy', 'британска територија индијског океана', 'lutanda lwa angeletele ku mbu wa indiya', 'ying lingindo yang de yu', 'британски територии в индийския океан', 'britanski indijskooceanski teritorij', 'britske indickooceanske uzemi', 'britaniya-hindistan okeanik territoriyasi', 'bresku indlandshafseyjar', 'wilayah inggris di samudra hindia', 'yankin birtaniya na tekun indiya', 'британські території індійського океану', 'territorio britannico dell\'oceano indiano', 'برطانوی بحرہند خطہ', 'britské indickooceánské území', 'io', 'criocha briotanacha an aigein indiagh', 'orileede etikun indiani ti ilu biritisi', 'bryttisca indisca garsecg landscipe', 'british indian ocean territory', 'బ్రిటిష్ భారతీయ ఓషన్ ప్రాంతం', 'britu indijas okeana teritorija', 'britansko indijska okeanska teritorija', 'ബ്രിട്ടീഷ് ഇന്ത്യന്‍ മഹാസമുദ്ര പ്രദേശം', 'teritoryo han britanya ha kalawdan indyano', 'үнді мұхитындағы британия аймағы', 'mabelé ya angɛlɛtɛ́lɛ na mbú ya indiya', 'territori britànic de loceà índic', 'teritoeio britannego de loceano indian', 'brit indiai-oceani teruelet', 'bizinga by\'ecago', 'tiriogaeth brydeinig cefnfor india', 'טריטוריה בריטית באוקיאנוס ההודי', 'إقليم المحيط الهندي البريطاني', 'ბრიტანული ტერიტორია ინდოეთის ოკეანეში', 'det britiske territorium i det indiske ocean', 'බ්‍රිතාන්‍ය ඉන්දීය සාගර ප්‍රාන්තය', 'برطانوی ہندوستانی سمندری خطہ', 'britaniya hind okeanı əraziləri', 'الإقليم البريطاني في المحيط الهندي', 'i̇ngiliz hint okyanusu bölgesi', 'britské územie v indickom oceáne', 'wilayah lautan hindi british', 'britiske område i det indiske hav', 'britanskaja territorija v indijskom okeane', 'wilayah inggreh di samudra hindia', 'britis indiyan mahasagara pradesa', 'britanska teritorija indijskog okeana', 'britské indickooceánske územie', 'wilayah samudra hindia britania', 'brytanskaja tehrytoryja u indyjskim akijane', 'britanya hint okyanusu topraklari', 'territori britanic de locea indic', 'indijos vandenyno britų sritis', 'territori britànic de l\'oceà índic', 'qlmrw brytanya dr aqyanws hnd', 'ਬਰਤਾਨਵੀ ਹਿੰਦ ਮਹਾਂਸਾਗਰ ਰਾਜਖੇਤਰ', 'lanh tho an gjo duong thuoc anh', 'críocha briotanacha an aigéin indiagh', 'britans\'ki teritorii indijs\'kogo okeanu', 'belayati hinda mahasagara ksetra', 'ब्रिटिश हिंद महासागरीय क्षेत्र', 'индийы фурды британийы территори', 'angilɛ ka ɛndu dugukolo', 'sese ti anglee na nguyaemae ti ennde', 'britisa hinda mahasagara ksetra', 'britaintɔwo ƒe india ƒudome nutome', 'sêse tî anglëe na ngûyämä tî ênnde', 'britse indiese oseaangebied', 'britanski teritorij indijskog oceana', 'terytorium brytyjskie oceanu indyjskiego', 'braitisba indiyana mahasagarara ancala', 'britenfo hɔn man wɔ india po no mu', 'territorio britanico de locián indico', 'britanska indookeanska teritorija', '英屬印度洋領地', 'ဗြိတိသျှ အိန္ဒြိယ သမုဒ္ဒရာ ပိုင်နက်', 'briti india ookeani ala', 'ब्रिटीश हिंदी महासागर क्षेत्र', 'thuộc địa anh tại ấn độ dương', 'બ્રિટિશ ઇન્ડિયન ઓશન ટેરિટરી', 'brittiska indiska oceanöarna', 'ବ୍ରିଟିଶ୍ ଭାରତୀୟ ସାମୁଦ୍ରିକ କ୍ଷେତ୍ର', '영국령 인도양 지역', 'британська територія в індійському океані', 'vretaniko edafos indikou okeanou', 'agbegbe okun india britani', 'baratanavi hida mahansagara rajakhetara', 'британска индоокеанска територия', 'brittiska indiska oceanoearna', 'territoires britanniques de l\'ocean indien', 'britanska teritorija u indijskom okeanu', 'wilayah inggréh di samudra hindia', 'pirittis intiyap perunkatal pakuti', 'tiriad meurvor indez breizh-veur', 'британска територија у индијском океану', 'brittilaeinen intian valtameren alue', 'britischs territorium im indischn ozean', 'potu fonua moana ʻinitia fakapilitānia', 'britis bharatiya samudrika ksetra', 'território britânico do oceano índico', 'பிரித்தானிய இந்தியப் பெருங்கடல் மண்டலம்'], 1471 | 'toponym': 'British Indian Ocean Territory', 1472 | 'featureCode': 'PCLD', 1473 | 'countryCode': 'IO', 1474 | 'featureClass': 'A', 1475 | 'hierarchy': { 1476 | 'features': [{ 1477 | 'geometry': null, 1478 | 'type': 'Feature', 1479 | 'properties': { 1480 | 'geoNameId': 6255147, 1481 | 'toponym': 'Asia', 1482 | 'hierarchy': { 1483 | 'features': [], 1484 | 'type': 'FeatureCollection' 1485 | }, 1486 | 'name': 'Indiana', 1487 | 'locationType': null, 1488 | 'positions': [21], 1489 | 'type': 'location' 1490 | } 1491 | }], 1492 | 'type': 'FeatureCollection' 1493 | }, 1494 | 'name': 'Indiana', 1495 | 'locationType': null, 1496 | 'positions': [21], 1497 | 'type': 'location' 1498 | } 1499 | }, { 1500 | 'geometry': { 1501 | 'coordinates': [-51.26047, -22.12587], 1502 | 'type': 'Point' 1503 | }, 1504 | 'type': 'Feature', 1505 | 'properties': { 1506 | 'geoNameId': 6322290, 1507 | 'alternateNames': ['indiana'], 1508 | 'toponym': 'Indiana', 1509 | 'featureCode': 'ADM2', 1510 | 'countryCode': 'BR', 1511 | 'featureClass': 'A', 1512 | 'hierarchy': { 1513 | 'features': [{ 1514 | 'geometry': null, 1515 | 'type': 'Feature', 1516 | 'properties': { 1517 | 'geoNameId': 3448433, 1518 | 'toponym': 'São Paulo', 1519 | 'hierarchy': { 1520 | 'features': [], 1521 | 'type': 'FeatureCollection' 1522 | }, 1523 | 'name': 'Indiana', 1524 | 'locationType': null, 1525 | 'positions': [21], 1526 | 'type': 'location' 1527 | } 1528 | }, { 1529 | 'geometry': null, 1530 | 'type': 'Feature', 1531 | 'properties': { 1532 | 'geoNameId': 3469034, 1533 | 'toponym': 'Brazil', 1534 | 'hierarchy': { 1535 | 'features': [], 1536 | 'type': 'FeatureCollection' 1537 | }, 1538 | 'name': 'Indiana', 1539 | 'locationType': null, 1540 | 'positions': [21], 1541 | 'type': 'location' 1542 | } 1543 | }, { 1544 | 'geometry': null, 1545 | 'type': 'Feature', 1546 | 'properties': { 1547 | 'geoNameId': 6255150, 1548 | 'toponym': 'South America', 1549 | 'hierarchy': { 1550 | 'features': [], 1551 | 'type': 'FeatureCollection' 1552 | }, 1553 | 'name': 'Indiana', 1554 | 'locationType': null, 1555 | 'positions': [21], 1556 | 'type': 'location' 1557 | } 1558 | }], 1559 | 'type': 'FeatureCollection' 1560 | }, 1561 | 'name': 'Indiana', 1562 | 'locationType': null, 1563 | 'positions': [21], 1564 | 'type': 'location' 1565 | } 1566 | }, { 1567 | 'geometry': { 1568 | 'coordinates': [-73.05136, -3.49989], 1569 | 'type': 'Point' 1570 | }, 1571 | 'type': 'Feature', 1572 | 'properties': { 1573 | 'geoNameId': 10346348, 1574 | 'alternateNames': ['indiana'], 1575 | 'toponym': 'Indiana', 1576 | 'featureCode': 'PPLA3', 1577 | 'countryCode': 'PE', 1578 | 'featureClass': 'P', 1579 | 'hierarchy': { 1580 | 'features': [{ 1581 | 'geometry': null, 1582 | 'type': 'Feature', 1583 | 'properties': { 1584 | 'geoNameId': 3694873, 1585 | 'toponym': 'Provincia de Maynas', 1586 | 'hierarchy': { 1587 | 'features': [], 1588 | 'type': 'FeatureCollection' 1589 | }, 1590 | 'name': 'Indiana', 1591 | 'locationType': null, 1592 | 'positions': [21], 1593 | 'type': 'location' 1594 | } 1595 | }, { 1596 | 'geometry': null, 1597 | 'type': 'Feature', 1598 | 'properties': { 1599 | 'geoNameId': 3695238, 1600 | 'toponym': 'Loreto', 1601 | 'hierarchy': { 1602 | 'features': [], 1603 | 'type': 'FeatureCollection' 1604 | }, 1605 | 'name': 'Indiana', 1606 | 'locationType': null, 1607 | 'positions': [21], 1608 | 'type': 'location' 1609 | } 1610 | }, { 1611 | 'geometry': null, 1612 | 'type': 'Feature', 1613 | 'properties': { 1614 | 'geoNameId': 3932488, 1615 | 'toponym': 'Peru', 1616 | 'hierarchy': { 1617 | 'features': [], 1618 | 'type': 'FeatureCollection' 1619 | }, 1620 | 'name': 'Indiana', 1621 | 'locationType': null, 1622 | 'positions': [21], 1623 | 'type': 'location' 1624 | } 1625 | }, { 1626 | 'geometry': null, 1627 | 'type': 'Feature', 1628 | 'properties': { 1629 | 'geoNameId': 6255150, 1630 | 'toponym': 'South America', 1631 | 'hierarchy': { 1632 | 'features': [], 1633 | 'type': 'FeatureCollection' 1634 | }, 1635 | 'name': 'Indiana', 1636 | 'locationType': null, 1637 | 'positions': [21], 1638 | 'type': 'location' 1639 | } 1640 | }], 1641 | 'type': 'FeatureCollection' 1642 | }, 1643 | 'name': 'Indiana', 1644 | 'locationType': null, 1645 | 'positions': [21], 1646 | 'type': 'location' 1647 | } 1648 | }, { 1649 | 'geometry': { 1650 | 'coordinates': [-87.85, 15.46667], 1651 | 'type': 'Point' 1652 | }, 1653 | 'type': 'Feature', 1654 | 'properties': { 1655 | 'geoNameId': 3608849, 1656 | 'alternateNames': ['indiana'], 1657 | 'toponym': 'Indiana', 1658 | 'featureCode': 'PPL', 1659 | 'countryCode': 'HN', 1660 | 'featureClass': 'P', 1661 | 'hierarchy': { 1662 | 'features': [{ 1663 | 'geometry': null, 1664 | 'type': 'Feature', 1665 | 'properties': { 1666 | 'geoNameId': 3613140, 1667 | 'toponym': 'Departamento de Cortés', 1668 | 'hierarchy': { 1669 | 'features': [], 1670 | 'type': 'FeatureCollection' 1671 | }, 1672 | 'name': 'Indiana', 1673 | 'locationType': null, 1674 | 'positions': [21], 1675 | 'type': 'location' 1676 | } 1677 | }, { 1678 | 'geometry': null, 1679 | 'type': 'Feature', 1680 | 'properties': { 1681 | 'geoNameId': 3608932, 1682 | 'toponym': 'Honduras', 1683 | 'hierarchy': { 1684 | 'features': [], 1685 | 'type': 'FeatureCollection' 1686 | }, 1687 | 'name': 'Indiana', 1688 | 'locationType': null, 1689 | 'positions': [21], 1690 | 'type': 'location' 1691 | } 1692 | }, { 1693 | 'geometry': null, 1694 | 'type': 'Feature', 1695 | 'properties': { 1696 | 'geoNameId': 6255149, 1697 | 'toponym': 'North America', 1698 | 'hierarchy': { 1699 | 'features': [], 1700 | 'type': 'FeatureCollection' 1701 | }, 1702 | 'name': 'Indiana', 1703 | 'locationType': null, 1704 | 'positions': [21], 1705 | 'type': 'location' 1706 | } 1707 | }], 1708 | 'type': 'FeatureCollection' 1709 | }, 1710 | 'name': 'Indiana', 1711 | 'locationType': null, 1712 | 'positions': [21], 1713 | 'type': 'location' 1714 | } 1715 | }, { 1716 | 'geometry': { 1717 | 'coordinates': [135.43994, -23.3383], 1718 | 'type': 'Point' 1719 | }, 1720 | 'type': 'Feature', 1721 | 'properties': { 1722 | 'geoNameId': 8644346, 1723 | 'alternateNames': ['indiana'], 1724 | 'toponym': 'Indiana', 1725 | 'featureCode': 'PPL', 1726 | 'countryCode': 'AU', 1727 | 'featureClass': 'P', 1728 | 'hierarchy': { 1729 | 'features': [{ 1730 | 'geometry': null, 1731 | 'type': 'Feature', 1732 | 'properties': { 1733 | 'geoNameId': 7839685, 1734 | 'toponym': 'Central Desert', 1735 | 'hierarchy': { 1736 | 'features': [], 1737 | 'type': 'FeatureCollection' 1738 | }, 1739 | 'name': 'Indiana', 1740 | 'locationType': null, 1741 | 'positions': [21], 1742 | 'type': 'location' 1743 | } 1744 | }, { 1745 | 'geometry': null, 1746 | 'type': 'Feature', 1747 | 'properties': { 1748 | 'geoNameId': 2064513, 1749 | 'toponym': 'Northern Territory', 1750 | 'hierarchy': { 1751 | 'features': [], 1752 | 'type': 'FeatureCollection' 1753 | }, 1754 | 'name': 'Indiana', 1755 | 'locationType': null, 1756 | 'positions': [21], 1757 | 'type': 'location' 1758 | } 1759 | }, { 1760 | 'geometry': null, 1761 | 'type': 'Feature', 1762 | 'properties': { 1763 | 'geoNameId': 2077456, 1764 | 'toponym': 'Australia', 1765 | 'hierarchy': { 1766 | 'features': [], 1767 | 'type': 'FeatureCollection' 1768 | }, 1769 | 'name': 'Indiana', 1770 | 'locationType': null, 1771 | 'positions': [21], 1772 | 'type': 'location' 1773 | } 1774 | }, { 1775 | 'geometry': null, 1776 | 'type': 'Feature', 1777 | 'properties': { 1778 | 'geoNameId': 6255151, 1779 | 'toponym': 'Oceania', 1780 | 'hierarchy': { 1781 | 'features': [], 1782 | 'type': 'FeatureCollection' 1783 | }, 1784 | 'name': 'Indiana', 1785 | 'locationType': null, 1786 | 'positions': [21], 1787 | 'type': 'location' 1788 | } 1789 | }], 1790 | 'type': 'FeatureCollection' 1791 | }, 1792 | 'name': 'Indiana', 1793 | 'locationType': null, 1794 | 'positions': [21], 1795 | 'type': 'location' 1796 | } 1797 | }, { 1798 | 'geometry': { 1799 | 'coordinates': [-51.25167, -22.17444], 1800 | 'type': 'Point' 1801 | }, 1802 | 'type': 'Feature', 1803 | 'properties': { 1804 | 'geoNameId': 3461294, 1805 | 'alternateNames': ['indiana'], 1806 | 'toponym': 'Indiana', 1807 | 'featureCode': 'PPL', 1808 | 'countryCode': 'BR', 1809 | 'featureClass': 'P', 1810 | 'hierarchy': { 1811 | 'features': [{ 1812 | 'geometry': null, 1813 | 'type': 'Feature', 1814 | 'properties': { 1815 | 'geoNameId': 6322290, 1816 | 'toponym': 'Indiana', 1817 | 'hierarchy': { 1818 | 'features': [], 1819 | 'type': 'FeatureCollection' 1820 | }, 1821 | 'name': 'Indiana', 1822 | 'locationType': null, 1823 | 'positions': [21], 1824 | 'type': 'location' 1825 | } 1826 | }, { 1827 | 'geometry': null, 1828 | 'type': 'Feature', 1829 | 'properties': { 1830 | 'geoNameId': 3448433, 1831 | 'toponym': 'São Paulo', 1832 | 'hierarchy': { 1833 | 'features': [], 1834 | 'type': 'FeatureCollection' 1835 | }, 1836 | 'name': 'Indiana', 1837 | 'locationType': null, 1838 | 'positions': [21], 1839 | 'type': 'location' 1840 | } 1841 | }, { 1842 | 'geometry': null, 1843 | 'type': 'Feature', 1844 | 'properties': { 1845 | 'geoNameId': 3469034, 1846 | 'toponym': 'Brazil', 1847 | 'hierarchy': { 1848 | 'features': [], 1849 | 'type': 'FeatureCollection' 1850 | }, 1851 | 'name': 'Indiana', 1852 | 'locationType': null, 1853 | 'positions': [21], 1854 | 'type': 'location' 1855 | } 1856 | }, { 1857 | 'geometry': null, 1858 | 'type': 'Feature', 1859 | 'properties': { 1860 | 'geoNameId': 6255150, 1861 | 'toponym': 'South America', 1862 | 'hierarchy': { 1863 | 'features': [], 1864 | 'type': 'FeatureCollection' 1865 | }, 1866 | 'name': 'Indiana', 1867 | 'locationType': null, 1868 | 'positions': [21], 1869 | 'type': 'location' 1870 | } 1871 | }], 1872 | 'type': 'FeatureCollection' 1873 | }, 1874 | 'name': 'Indiana', 1875 | 'locationType': null, 1876 | 'positions': [21], 1877 | 'type': 'location' 1878 | } 1879 | }, { 1880 | 'geometry': { 1881 | 'coordinates': [121.0773, 16.3332], 1882 | 'type': 'Point' 1883 | }, 1884 | 'type': 'Feature', 1885 | 'properties': { 1886 | 'geoNameId': 1710762, 1887 | 'alternateNames': ['indiana'], 1888 | 'toponym': 'Indiana', 1889 | 'featureCode': 'PPL', 1890 | 'countryCode': 'PH', 1891 | 'featureClass': 'P', 1892 | 'hierarchy': { 1893 | 'features': [{ 1894 | 'geometry': null, 1895 | 'type': 'Feature', 1896 | 'properties': { 1897 | 'geoNameId': 1697456, 1898 | 'toponym': 'Province of Nueva Vizcaya', 1899 | 'hierarchy': { 1900 | 'features': [], 1901 | 'type': 'FeatureCollection' 1902 | }, 1903 | 'name': 'Indiana', 1904 | 'locationType': null, 1905 | 'positions': [21], 1906 | 'type': 'location' 1907 | } 1908 | }, { 1909 | 'geometry': null, 1910 | 'type': 'Feature', 1911 | 'properties': { 1912 | 'geoNameId': 7521297, 1913 | 'toponym': 'Cagayan Valley', 1914 | 'hierarchy': { 1915 | 'features': [], 1916 | 'type': 'FeatureCollection' 1917 | }, 1918 | 'name': 'Indiana', 1919 | 'locationType': null, 1920 | 'positions': [21], 1921 | 'type': 'location' 1922 | } 1923 | }, { 1924 | 'geometry': null, 1925 | 'type': 'Feature', 1926 | 'properties': { 1927 | 'geoNameId': 1694008, 1928 | 'toponym': 'Philippines', 1929 | 'hierarchy': { 1930 | 'features': [], 1931 | 'type': 'FeatureCollection' 1932 | }, 1933 | 'name': 'Indiana', 1934 | 'locationType': null, 1935 | 'positions': [21], 1936 | 'type': 'location' 1937 | } 1938 | }, { 1939 | 'geometry': null, 1940 | 'type': 'Feature', 1941 | 'properties': { 1942 | 'geoNameId': 6255147, 1943 | 'toponym': 'Asia', 1944 | 'hierarchy': { 1945 | 'features': [], 1946 | 'type': 'FeatureCollection' 1947 | }, 1948 | 'name': 'Indiana', 1949 | 'locationType': null, 1950 | 'positions': [21], 1951 | 'type': 'location' 1952 | } 1953 | }], 1954 | 'type': 'FeatureCollection' 1955 | }, 1956 | 'name': 'Indiana', 1957 | 'locationType': null, 1958 | 'positions': [21], 1959 | 'type': 'location' 1960 | } 1961 | }, { 1962 | 'geometry': { 1963 | 'coordinates': [-93.0252, 41.20444], 1964 | 'type': 'Point' 1965 | }, 1966 | 'type': 'Feature', 1967 | 'properties': { 1968 | 'geoNameId': 4861712, 1969 | 'alternateNames': ['indiana'], 1970 | 'toponym': 'Indiana', 1971 | 'featureCode': 'PPL', 1972 | 'countryCode': 'US', 1973 | 'featureClass': 'P', 1974 | 'hierarchy': { 1975 | 'features': [{ 1976 | 'geometry': null, 1977 | 'type': 'Feature', 1978 | 'properties': { 1979 | 'geoNameId': 4866274, 1980 | 'toponym': 'Marion County', 1981 | 'hierarchy': { 1982 | 'features': [], 1983 | 'type': 'FeatureCollection' 1984 | }, 1985 | 'name': 'Indiana', 1986 | 'locationType': null, 1987 | 'positions': [21], 1988 | 'type': 'location' 1989 | } 1990 | }, { 1991 | 'geometry': null, 1992 | 'type': 'Feature', 1993 | 'properties': { 1994 | 'geoNameId': 4862182, 1995 | 'toponym': 'Iowa', 1996 | 'hierarchy': { 1997 | 'features': [], 1998 | 'type': 'FeatureCollection' 1999 | }, 2000 | 'name': 'Indiana', 2001 | 'locationType': null, 2002 | 'positions': [21], 2003 | 'type': 'location' 2004 | } 2005 | }, { 2006 | 'geometry': null, 2007 | 'type': 'Feature', 2008 | 'properties': { 2009 | 'geoNameId': 6252001, 2010 | 'toponym': 'United States', 2011 | 'hierarchy': { 2012 | 'features': [], 2013 | 'type': 'FeatureCollection' 2014 | }, 2015 | 'name': 'Indiana', 2016 | 'locationType': null, 2017 | 'positions': [21], 2018 | 'type': 'location' 2019 | } 2020 | }, { 2021 | 'geometry': null, 2022 | 'type': 'Feature', 2023 | 'properties': { 2024 | 'geoNameId': 6255149, 2025 | 'toponym': 'North America', 2026 | 'hierarchy': { 2027 | 'features': [], 2028 | 'type': 'FeatureCollection' 2029 | }, 2030 | 'name': 'Indiana', 2031 | 'locationType': null, 2032 | 'positions': [21], 2033 | 'type': 'location' 2034 | } 2035 | }], 2036 | 'type': 'FeatureCollection' 2037 | }, 2038 | 'name': 'Indiana', 2039 | 'locationType': null, 2040 | 'positions': [21], 2041 | 'type': 'location' 2042 | } 2043 | }, { 2044 | 'geometry': { 2045 | 'coordinates': [-58.67773, 6.91336], 2046 | 'type': 'Point' 2047 | }, 2048 | 'type': 'Feature', 2049 | 'properties': { 2050 | 'geoNameId': 3378256, 2051 | 'alternateNames': ['indina', 'indiana'], 2052 | 'toponym': 'Indiana', 2053 | 'featureCode': 'LCTY', 2054 | 'countryCode': 'GY', 2055 | 'featureClass': 'L', 2056 | 'hierarchy': { 2057 | 'features': [{ 2058 | 'geometry': null, 2059 | 'type': 'Feature', 2060 | 'properties': { 2061 | 'geoNameId': 3376407, 2062 | 'toponym': 'Pomeroon-Supenaam Region', 2063 | 'hierarchy': { 2064 | 'features': [], 2065 | 'type': 'FeatureCollection' 2066 | }, 2067 | 'name': 'Indiana', 2068 | 'locationType': null, 2069 | 'positions': [21], 2070 | 'type': 'location' 2071 | } 2072 | }, { 2073 | 'geometry': null, 2074 | 'type': 'Feature', 2075 | 'properties': { 2076 | 'geoNameId': 3378535, 2077 | 'toponym': 'Guyana', 2078 | 'hierarchy': { 2079 | 'features': [], 2080 | 'type': 'FeatureCollection' 2081 | }, 2082 | 'name': 'Indiana', 2083 | 'locationType': null, 2084 | 'positions': [21], 2085 | 'type': 'location' 2086 | } 2087 | }, { 2088 | 'geometry': null, 2089 | 'type': 'Feature', 2090 | 'properties': { 2091 | 'geoNameId': 6255150, 2092 | 'toponym': 'South America', 2093 | 'hierarchy': { 2094 | 'features': [], 2095 | 'type': 'FeatureCollection' 2096 | }, 2097 | 'name': 'Indiana', 2098 | 'locationType': null, 2099 | 'positions': [21], 2100 | 'type': 'location' 2101 | } 2102 | }], 2103 | 'type': 'FeatureCollection' 2104 | }, 2105 | 'name': 'Indiana', 2106 | 'locationType': null, 2107 | 'positions': [21], 2108 | 'type': 'location' 2109 | } 2110 | }, { 2111 | 'geometry': { 2112 | 'coordinates': [-3.69676, 40.24645], 2113 | 'type': 'Point' 2114 | }, 2115 | 'type': 'Feature', 2116 | 'properties': { 2117 | 'geoNameId': 9406284, 2118 | 'alternateNames': ['indiana'], 2119 | 'toponym': 'Indiana', 2120 | 'featureCode': 'HTL', 2121 | 'countryCode': 'ES', 2122 | 'featureClass': 'S', 2123 | 'hierarchy': { 2124 | 'features': [{ 2125 | 'geometry': null, 2126 | 'type': 'Feature', 2127 | 'properties': { 2128 | 'geoNameId': 6355233, 2129 | 'toponym': 'Provincia de Madrid', 2130 | 'hierarchy': { 2131 | 'features': [], 2132 | 'type': 'FeatureCollection' 2133 | }, 2134 | 'name': 'Indiana', 2135 | 'locationType': null, 2136 | 'positions': [21], 2137 | 'type': 'location' 2138 | } 2139 | }, { 2140 | 'geometry': null, 2141 | 'type': 'Feature', 2142 | 'properties': { 2143 | 'geoNameId': 3117732, 2144 | 'toponym': 'Comunidad de Madrid', 2145 | 'hierarchy': { 2146 | 'features': [], 2147 | 'type': 'FeatureCollection' 2148 | }, 2149 | 'name': 'Indiana', 2150 | 'locationType': null, 2151 | 'positions': [21], 2152 | 'type': 'location' 2153 | } 2154 | }, { 2155 | 'geometry': null, 2156 | 'type': 'Feature', 2157 | 'properties': { 2158 | 'geoNameId': 2510769, 2159 | 'toponym': 'Spain', 2160 | 'hierarchy': { 2161 | 'features': [], 2162 | 'type': 'FeatureCollection' 2163 | }, 2164 | 'name': 'Indiana', 2165 | 'locationType': null, 2166 | 'positions': [21], 2167 | 'type': 'location' 2168 | } 2169 | }, { 2170 | 'geometry': null, 2171 | 'type': 'Feature', 2172 | 'properties': { 2173 | 'geoNameId': 6255148, 2174 | 'toponym': 'Europe', 2175 | 'hierarchy': { 2176 | 'features': [], 2177 | 'type': 'FeatureCollection' 2178 | }, 2179 | 'name': 'Indiana', 2180 | 'locationType': null, 2181 | 'positions': [21], 2182 | 'type': 'location' 2183 | } 2184 | }], 2185 | 'type': 'FeatureCollection' 2186 | }, 2187 | 'name': 'Indiana', 2188 | 'locationType': null, 2189 | 'positions': [21], 2190 | 'type': 'location' 2191 | } 2192 | }, { 2193 | 'geometry': { 2194 | 'coordinates': [151.28688, -29.9269], 2195 | 'type': 'Point' 2196 | }, 2197 | 'type': 'Feature', 2198 | 'properties': { 2199 | 'geoNameId': 8808786, 2200 | 'alternateNames': ['indiana'], 2201 | 'toponym': 'Indiana', 2202 | 'featureCode': 'HMSD', 2203 | 'countryCode': 'AU', 2204 | 'featureClass': 'S', 2205 | 'hierarchy': { 2206 | 'features': [{ 2207 | 'geometry': null, 2208 | 'type': 'Feature', 2209 | 'properties': { 2210 | 'geoNameId': 7839726, 2211 | 'toponym': 'Guyra', 2212 | 'hierarchy': { 2213 | 'features': [], 2214 | 'type': 'FeatureCollection' 2215 | }, 2216 | 'name': 'Indiana', 2217 | 'locationType': null, 2218 | 'positions': [21], 2219 | 'type': 'location' 2220 | } 2221 | }, { 2222 | 'geometry': null, 2223 | 'type': 'Feature', 2224 | 'properties': { 2225 | 'geoNameId': 2155400, 2226 | 'toponym': 'State of New South Wales', 2227 | 'hierarchy': { 2228 | 'features': [], 2229 | 'type': 'FeatureCollection' 2230 | }, 2231 | 'name': 'Indiana', 2232 | 'locationType': null, 2233 | 'positions': [21], 2234 | 'type': 'location' 2235 | } 2236 | }, { 2237 | 'geometry': null, 2238 | 'type': 'Feature', 2239 | 'properties': { 2240 | 'geoNameId': 2077456, 2241 | 'toponym': 'Australia', 2242 | 'hierarchy': { 2243 | 'features': [], 2244 | 'type': 'FeatureCollection' 2245 | }, 2246 | 'name': 'Indiana', 2247 | 'locationType': null, 2248 | 'positions': [21], 2249 | 'type': 'location' 2250 | } 2251 | }, { 2252 | 'geometry': null, 2253 | 'type': 'Feature', 2254 | 'properties': { 2255 | 'geoNameId': 6255151, 2256 | 'toponym': 'Oceania', 2257 | 'hierarchy': { 2258 | 'features': [], 2259 | 'type': 'FeatureCollection' 2260 | }, 2261 | 'name': 'Indiana', 2262 | 'locationType': null, 2263 | 'positions': [21], 2264 | 'type': 'location' 2265 | } 2266 | }], 2267 | 'type': 'FeatureCollection' 2268 | }, 2269 | 'name': 'Indiana', 2270 | 'locationType': null, 2271 | 'positions': [21], 2272 | 'type': 'location' 2273 | } 2274 | }, { 2275 | 'geometry': { 2276 | 'coordinates': [135.43968, -23.3291], 2277 | 'type': 'Point' 2278 | }, 2279 | 'type': 'Feature', 2280 | 'properties': { 2281 | 'geoNameId': 8815956, 2282 | 'alternateNames': ['indiana'], 2283 | 'toponym': 'Indiana', 2284 | 'featureCode': 'HMSD', 2285 | 'countryCode': 'AU', 2286 | 'featureClass': 'S', 2287 | 'hierarchy': { 2288 | 'features': [{ 2289 | 'geometry': null, 2290 | 'type': 'Feature', 2291 | 'properties': { 2292 | 'geoNameId': 7839685, 2293 | 'toponym': 'Central Desert', 2294 | 'hierarchy': { 2295 | 'features': [], 2296 | 'type': 'FeatureCollection' 2297 | }, 2298 | 'name': 'Indiana', 2299 | 'locationType': null, 2300 | 'positions': [21], 2301 | 'type': 'location' 2302 | } 2303 | }, { 2304 | 'geometry': null, 2305 | 'type': 'Feature', 2306 | 'properties': { 2307 | 'geoNameId': 2064513, 2308 | 'toponym': 'Northern Territory', 2309 | 'hierarchy': { 2310 | 'features': [], 2311 | 'type': 'FeatureCollection' 2312 | }, 2313 | 'name': 'Indiana', 2314 | 'locationType': null, 2315 | 'positions': [21], 2316 | 'type': 'location' 2317 | } 2318 | }, { 2319 | 'geometry': null, 2320 | 'type': 'Feature', 2321 | 'properties': { 2322 | 'geoNameId': 2077456, 2323 | 'toponym': 'Australia', 2324 | 'hierarchy': { 2325 | 'features': [], 2326 | 'type': 'FeatureCollection' 2327 | }, 2328 | 'name': 'Indiana', 2329 | 'locationType': null, 2330 | 'positions': [21], 2331 | 'type': 'location' 2332 | } 2333 | }, { 2334 | 'geometry': null, 2335 | 'type': 'Feature', 2336 | 'properties': { 2337 | 'geoNameId': 6255151, 2338 | 'toponym': 'Oceania', 2339 | 'hierarchy': { 2340 | 'features': [], 2341 | 'type': 'FeatureCollection' 2342 | }, 2343 | 'name': 'Indiana', 2344 | 'locationType': null, 2345 | 'positions': [21], 2346 | 'type': 'location' 2347 | } 2348 | }], 2349 | 'type': 'FeatureCollection' 2350 | }, 2351 | 'name': 'Indiana', 2352 | 'locationType': null, 2353 | 'positions': [21], 2354 | 'type': 'location' 2355 | } 2356 | }, { 2357 | 'geometry': { 2358 | 'coordinates': [31.22952, 30.03147], 2359 | 'type': 'Point' 2360 | }, 2361 | 'type': 'Feature', 2362 | 'properties': { 2363 | 'geoNameId': 9953265, 2364 | 'alternateNames': ['indiana'], 2365 | 'toponym': 'Indiana', 2366 | 'featureCode': 'HTL', 2367 | 'countryCode': 'EG', 2368 | 'featureClass': 'S', 2369 | 'hierarchy': { 2370 | 'features': [{ 2371 | 'geometry': null, 2372 | 'type': 'Feature', 2373 | 'properties': { 2374 | 'geoNameId': 360631, 2375 | 'toponym': 'Cairo Governorate', 2376 | 'hierarchy': { 2377 | 'features': [], 2378 | 'type': 'FeatureCollection' 2379 | }, 2380 | 'name': 'Indiana', 2381 | 'locationType': null, 2382 | 'positions': [21], 2383 | 'type': 'location' 2384 | } 2385 | }, { 2386 | 'geometry': null, 2387 | 'type': 'Feature', 2388 | 'properties': { 2389 | 'geoNameId': 357994, 2390 | 'toponym': 'Egypt', 2391 | 'hierarchy': { 2392 | 'features': [], 2393 | 'type': 'FeatureCollection' 2394 | }, 2395 | 'name': 'Indiana', 2396 | 'locationType': null, 2397 | 'positions': [21], 2398 | 'type': 'location' 2399 | } 2400 | }, { 2401 | 'geometry': null, 2402 | 'type': 'Feature', 2403 | 'properties': { 2404 | 'geoNameId': 6255146, 2405 | 'toponym': 'Africa', 2406 | 'hierarchy': { 2407 | 'features': [], 2408 | 'type': 'FeatureCollection' 2409 | }, 2410 | 'name': 'Indiana', 2411 | 'locationType': null, 2412 | 'positions': [21], 2413 | 'type': 'location' 2414 | } 2415 | }], 2416 | 'type': 'FeatureCollection' 2417 | }, 2418 | 'name': 'Indiana', 2419 | 'locationType': null, 2420 | 'positions': [21], 2421 | 'type': 'location' 2422 | } 2423 | }], 2424 | 'type': 'FeatureCollection' 2425 | } 2426 | } 2427 | }], 2428 | 'type': 'FeatureCollection' 2429 | }; 2430 | -------------------------------------------------------------------------------- /example/images/circle-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZijingPeng/leaflet-tooltip-layout/d9bf9793947b29ea26e5ba5474c4ad8c5d3b176b/example/images/circle-grey.png -------------------------------------------------------------------------------- /example/images/circle-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZijingPeng/leaflet-tooltip-layout/d9bf9793947b29ea26e5ba5474c4ad8c5d3b176b/example/images/circle-red.png -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | leaflet-plugin-tooltip-layout DEMO 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import * as L from 'leaflet'; 2 | import { 3 | initialize, 4 | resetMarker, 5 | getMarkers, 6 | getLine 7 | } from '../lib'; 8 | import { 9 | icon, 10 | iconlarge 11 | } from './Icon'; 12 | import testData from './TestData'; 13 | import config from './Config'; 14 | 15 | // initialize map 16 | const mapContainerElId = 'map-container'; 17 | let map = L.map(mapContainerElId, { 18 | zoomSnap: 0.25 19 | }).setView([40.2672, -86.1349], 4); 20 | 21 | // background layer 22 | L.tileLayer(config.mapTileLayerUrlTemplate, { 23 | maxZoom: 18, 24 | id: 'mapbox.streets' 25 | }).addTo(map); 26 | 27 | let markerList = []; 28 | 29 | L.geoJSON(testData, { 30 | pointToLayer: function (feature, latlng) { 31 | let marker = L.marker(latlng, { 32 | icon: icon 33 | }); 34 | return marker; 35 | }, 36 | onEachFeature(feature, layer) { 37 | feature.properties.alternates.features.forEach((iter, index) => { 38 | let name = iter.properties.name + ' ' + index; 39 | let coord = [iter.geometry.coordinates[1], iter.geometry.coordinates[0]]; 40 | let marker = L.marker(coord, { 41 | icon: icon 42 | }).addTo(map); 43 | marker.bindTooltip(name); 44 | resetMarker(marker); 45 | }); 46 | } 47 | }).addTo(map); 48 | 49 | // when mouse hover, icon gets larger 50 | function addMarkerHoverEvents() { 51 | function onMarkerMouseover(marker, tooltipDom) { 52 | return function () { 53 | marker.setIcon(iconlarge); 54 | marker._icon.style.zIndex = 999; 55 | tooltipDom.style.zIndex = 999; 56 | tooltipDom.style.border = '2px solid #039BE5'; 57 | getLine(marker) && getLine(marker).setStyle({ 58 | color: '#039BE5' 59 | }); 60 | }; 61 | } 62 | 63 | function onMarkerMouseout(marker, tooltipDom) { 64 | return function () { 65 | marker.setIcon(icon); 66 | marker._icon.style.zIndex = ''; 67 | tooltipDom.style.zIndex = ''; 68 | tooltipDom.style.border = ''; 69 | tooltipDom.style.borderColor = ''; 70 | getLine(marker) && getLine(marker).setStyle({ 71 | color: '#90A4AE' 72 | }); 73 | }; 74 | } 75 | 76 | var i, marker, tooltip; 77 | var markerList = getMarkers(); 78 | for (i = 0; i < markerList.length; i++) { 79 | marker = markerList[i]; 80 | tooltip = marker.getTooltip(); 81 | marker._icon.addEventListener('mouseover', onMarkerMouseover(marker, tooltip._container)); 82 | marker._icon.addEventListener('mouseout', onMarkerMouseout(marker, tooltip._container)); 83 | tooltip._container.addEventListener('mouseover', onMarkerMouseover(marker, tooltip._container)); 84 | tooltip._container.addEventListener('mouseout', onMarkerMouseout(marker, tooltip._container)); 85 | } 86 | } 87 | addMarkerHoverEvents(); 88 | 89 | // when trigger HMR, just full reload page because leaflet.js is already init 90 | if (module.hot) { 91 | module.hot.dispose(() => { 92 | location.reload(); 93 | }); 94 | 95 | module.hot.accept(() => { 96 | location.reload(); 97 | }); 98 | } 99 | 100 | function onPolylineCreated(ply) { 101 | ply.setStyle({ 102 | color: '#90A4AE' 103 | }) 104 | } 105 | 106 | // init plugin 107 | initialize(map, onPolylineCreated); 108 | -------------------------------------------------------------------------------- /example/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | /* translate animation */ 7 | .leaflet-tooltip { 8 | transition: transform 0.3s ease-in-out; 9 | } 10 | 11 | /* remove arrow in tooltips */ 12 | .leaflet-tooltip-top:before, 13 | .leaflet-tooltip-bottom:before, 14 | .leaflet-tooltip-left:before, 15 | .leaflet-tooltip-right:before { 16 | visibility: hidden; 17 | } 18 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | (function (factory, window) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['leaflet'], factory); 4 | } else if (typeof exports === 'object') { 5 | module.exports = factory(require('leaflet')); 6 | } 7 | if (typeof window !== 'undefined' && window.L) { 8 | window.L.tooltipLayout = factory(L); 9 | } 10 | })(function (L) { 11 | var TooltipLayout = {}; 12 | 13 | // global variables 14 | var map; 15 | var markerList = []; // all markers here 16 | var polylineList = []; // all polylines here 17 | 18 | // events 19 | var _onPolylineCreated = null; // will be called after polyline has been created 20 | 21 | function initialize(leafletMap, onPolylineCreated) { 22 | map = leafletMap; 23 | markerList = []; 24 | polylineList = []; 25 | 26 | //default style 27 | if (onPolylineCreated) { 28 | _onPolylineCreated = onPolylineCreated; 29 | } else { 30 | _onPolylineCreated = (ply) => { 31 | ply.setStyle({ 32 | color: '#40809d', 33 | }); 34 | }; 35 | } 36 | 37 | setRandomPos(map); 38 | layoutByForce(); 39 | setEdgePosition(); 40 | drawLine(map); 41 | // event registrations 42 | map.on('zoomstart', function() { 43 | removeAllMarkers() 44 | removeAllPolyline(map); 45 | }); 46 | 47 | map.on('zoomend', function() { 48 | redrawLines(true); 49 | }); 50 | 51 | map.on('dragend', function() { 52 | redrawLines(); 53 | }); 54 | 55 | map.on('resize', function() { 56 | redrawLines(); 57 | }); 58 | } 59 | 60 | function redrawLines(maintainAllPolyline) { 61 | if (!maintainAllPolyline) { 62 | removeAllPolyline(map); 63 | } 64 | setRandomPos(map); 65 | layoutByForce(); 66 | setEdgePosition(); 67 | drawLine(map); 68 | } 69 | 70 | function addMarker(marker) { 71 | markerList.push(marker) 72 | } 73 | 74 | function deleteMarker(marker) { 75 | let i = markerList.indexOf(marker); 76 | if (i !== -1) { 77 | markerList.splice(i, 1) 78 | } 79 | } 80 | 81 | function resetMarker(marker) { 82 | var name = marker.getTooltip().getContent(); 83 | var options = marker.getTooltip().options; 84 | marker.unbindTooltip(); 85 | 86 | marker.bindTooltip(name, { 87 | pane: options.pane, 88 | offset: options.offset, 89 | className: options.className, 90 | permanent: true, 91 | interactive: true, 92 | direction: 'left', 93 | sticky: 'none', 94 | opacity: options.opacity 95 | }); 96 | markerList.push(marker); 97 | } 98 | 99 | function getMarkers() { 100 | return markerList; 101 | } 102 | 103 | function setMarkers(arr) { 104 | markerList = arr; 105 | } 106 | 107 | function getLine(marker) { 108 | return marker.__ply; 109 | } 110 | 111 | function removeAllPolyline(map) { 112 | var i; 113 | for (i = 0; i < polylineList.length; i++) { 114 | map.removeLayer(polylineList[i]); 115 | } 116 | polylineList = []; 117 | } 118 | 119 | function removeAllMarkers() { 120 | getMarkers().forEach((marker) => marker.remove()); 121 | markerList = []; 122 | } 123 | 124 | function resetMapContent() { 125 | removeAllMarkers(); 126 | removeAllPolyline(map); 127 | } 128 | 129 | 130 | 131 | /** 132 | * Draw lines between markers and tooltips 133 | * @param map leaflet map 134 | */ 135 | function drawLine(map) { 136 | removeAllPolyline(map); 137 | for (var i = 0; i < markerList.length; i++) { 138 | var marker = markerList[i]; 139 | var markerDom = marker._icon; 140 | var markerPosition = getPosition(markerDom); 141 | var label = marker.getTooltip(); 142 | 143 | var labelDom = label._container; 144 | var labelPosition = getPosition(labelDom); 145 | 146 | var x1 = labelPosition.x; 147 | var y1 = labelPosition.y; 148 | 149 | var x = markerPosition.x; 150 | var y = markerPosition.y; 151 | 152 | x1 -= 5; 153 | y1 += 2; 154 | if (x1 - x !== 0 || y1 - y !== 0) { 155 | if (x1 + labelDom.offsetWidth < markerPosition.x) { 156 | x1 += labelDom.offsetWidth; 157 | } 158 | if (y1 + labelDom.offsetHeight < markerPosition.y) { 159 | y1 += labelDom.offsetHeight; 160 | } 161 | var lineDest = L.point(x1, y1); 162 | var destLatLng = map.layerPointToLatLng(lineDest); 163 | 164 | setTimeout( 165 | ((marker, destLatLng) => () => { 166 | let ply = L.polyline([marker.getLatLng(), destLatLng]); 167 | _onPolylineCreated && _onPolylineCreated(ply); 168 | marker.__ply = ply; 169 | polylineList.push(ply); 170 | ply.addTo(map); 171 | })(marker, destLatLng), 172 | 0 173 | ); 174 | } 175 | } 176 | } 177 | 178 | function setRandomPos() { 179 | for (var i = 0; i < markerList.length; i++) { 180 | var marker = markerList[i]; 181 | var label = marker.getTooltip(); 182 | var labelDom = label._container; 183 | var markerDom = marker._icon; 184 | var markerPosition = getPosition(markerDom); 185 | // var angle = Math.floor(Math.random() * 19 + 1) * 2 * Math.PI / 20; 186 | var angle = ((2 * Math.PI) / 6) * i; 187 | var x = markerPosition.x; 188 | var y = markerPosition.y; 189 | var dest = L.point( 190 | Math.ceil(x + 50 * Math.sin(angle)), 191 | Math.ceil(y + 50 * Math.cos(angle)) 192 | ); 193 | L.DomUtil.setPosition(labelDom, dest); 194 | } 195 | } 196 | 197 | function scaleTo(a, b) { 198 | return L.point(a.x * b.x, a.y * b.y); 199 | } 200 | 201 | function normalize(a) { 202 | var l = a.distanceTo(L.point(0, 0)); 203 | if (l === 0) { 204 | return a; 205 | } 206 | return L.point(a.x / l, a.y / l); 207 | } 208 | 209 | function fa(x, k) { 210 | return (x * x) / k; 211 | } 212 | 213 | function fr(x, k) { 214 | return (k * k) / x; 215 | } 216 | 217 | /** 218 | * get position form el.style.transform 219 | */ 220 | function getPosition(el) { 221 | // Failsafe to prevent element null 222 | if(el) { 223 | var translateString = el.style.transform 224 | .split('(')[1] 225 | .split(')')[0] 226 | .split(','); 227 | return L.point(parseInt(translateString[0]), parseInt(translateString[1])); 228 | } else { 229 | return L.point(0,0) 230 | } 231 | } 232 | 233 | /** 234 | * t is the temperature in the system 235 | */ 236 | function computePositionStep(t) { 237 | var area = (window.innerWidth * window.innerHeight) / 10; 238 | var k = Math.sqrt(area / markerList.length); 239 | var dpos = L.point(0, 0); 240 | var v_pos; 241 | var v; 242 | var i; 243 | 244 | for (i = 0; i < markerList.length; i++) { 245 | v = markerList[i]; 246 | // get position of label v 247 | v.disp = L.point(0, 0); 248 | v_pos = getPosition(v.getTooltip()._container); 249 | 250 | // compute gravitational force 251 | for (var j = 0; j < markerList.length; j++) { 252 | var u = markerList[j]; 253 | if (i !== j) { 254 | var u_pos = getPosition(u.getTooltip()._container); 255 | dpos = v_pos.subtract(u_pos); 256 | if (dpos !== 0) { 257 | v.disp = v.disp.add( 258 | normalize(dpos).multiplyBy(fr(dpos.distanceTo(L.point(0, 0)), k)) 259 | ); 260 | } 261 | } 262 | } 263 | } 264 | 265 | // compute force between marker and tooltip 266 | for (i = 0; i < markerList.length; i++) { 267 | v = markerList[i]; 268 | v_pos = getPosition(v.getTooltip()._container); 269 | dpos = v_pos.subtract(getPosition(v._icon)); 270 | v.disp = v.disp.subtract( 271 | normalize(dpos).multiplyBy(fa(dpos.distanceTo(L.point(0, 0)), k)) 272 | ); 273 | } 274 | 275 | // calculate layout 276 | for (i = 0; i < markerList.length; i++) { 277 | var disp = markerList[i].disp; 278 | var p = getPosition(markerList[i].getTooltip()._container); 279 | var d = scaleTo( 280 | normalize(disp), 281 | L.point(Math.min(Math.abs(disp.x), t), Math.min(Math.abs(disp.y), t)) 282 | ); 283 | p = p.add(d); 284 | p = L.point(Math.ceil(p.x), Math.ceil(p.y)); 285 | L.DomUtil.setTransform(markerList[i].getTooltip()._container, p); 286 | } 287 | } 288 | 289 | function layoutByForce() { 290 | var start = Math.ceil(window.innerWidth / 10); 291 | var times = 50; 292 | var t; 293 | for (var i = 0; i < times; i += 1) { 294 | t = start * (1 - i / (times - 1)); 295 | computePositionStep(t); 296 | } 297 | 298 | for (i = 0; i < markerList.length; i++) { 299 | var p = getPosition(markerList[i].getTooltip()._container); 300 | var width = markerList[i].getTooltip()._container.offsetWidth; 301 | var height = markerList[i].getTooltip()._container.offsetHeight; 302 | p = L.point(Math.ceil(p.x - width / 2), Math.ceil(p.y - height / 2)); 303 | L.DomUtil.setTransform(markerList[i].getTooltip()._container, p); 304 | } 305 | } 306 | 307 | function setEdgePosition() { 308 | var bounds = map.getBounds(); 309 | var northWest = map.latLngToLayerPoint(bounds.getNorthWest()); 310 | var southEast = map.latLngToLayerPoint(bounds.getSouthEast()); 311 | 312 | for (let i = 0; i < markerList.length; i++) { 313 | var tooltip = getPosition(markerList[i].getTooltip()._container); 314 | var marker = getPosition(markerList[i]._icon); 315 | var width = markerList[i].getTooltip()._container.offsetWidth; 316 | var height = markerList[i].getTooltip()._container.offsetHeight; 317 | 318 | var isEdge = false; 319 | if (marker.x > northWest.x && tooltip.x < northWest.x) { 320 | tooltip.x = northWest.x; 321 | isEdge = true; 322 | } else if (marker.x < southEast.x && tooltip.x > southEast.x - width) { 323 | tooltip.x = southEast.x - width; 324 | isEdge = true; 325 | } 326 | 327 | if (marker.y > northWest.y && tooltip.y < northWest.y) { 328 | tooltip.y = northWest.y; 329 | isEdge = true; 330 | } else if (marker.y < southEast.y && tooltip.y > southEast.y - height) { 331 | tooltip.y = southEast.y - height; 332 | isEdge = true; 333 | } 334 | 335 | if (!isEdge) { 336 | if (marker.x < northWest.x && tooltip.x > northWest.x - width) { 337 | tooltip.x = northWest.x - width; 338 | } else if (marker.x > southEast.x && tooltip.x < southEast.x) { 339 | tooltip.x = southEast.x; 340 | } 341 | 342 | if (marker.y < northWest.y && tooltip.y > northWest.y - height) { 343 | tooltip.y = northWest.y - height; 344 | } else if (marker.y > southEast.y && tooltip.y < southEast.y) { 345 | tooltip.y = southEast.y; 346 | } 347 | } 348 | 349 | L.DomUtil.setTransform(markerList[i].getTooltip()._container, tooltip); 350 | } 351 | } 352 | 353 | TooltipLayout['initialize'] = initialize; 354 | TooltipLayout['redrawLines'] = redrawLines; 355 | TooltipLayout['resetMarker'] = resetMarker; 356 | TooltipLayout['getMarkers'] = getMarkers; 357 | TooltipLayout['setMarkers'] = setMarkers; 358 | TooltipLayout['addMarker'] = addMarker; 359 | TooltipLayout['deleteMarker'] = deleteMarker; 360 | TooltipLayout['getLine'] = getLine; 361 | TooltipLayout['removeAllPolyline'] = removeAllPolyline; 362 | TooltipLayout['removeAllMarkers'] = removeAllMarkers 363 | TooltipLayout['resetMapContent'] = resetMapContent; 364 | 365 | return TooltipLayout; 366 | }, window); 367 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-tooltip-layout", 3 | "version": "0.1.2", 4 | "description": "Avoid tooltip overlapping and make users find out the relationship between each tooltip and marker easily", 5 | "private": false, 6 | "main": "./lib/index.js", 7 | "scripts": { 8 | "serve": "cross-env NODE_ENV=development parcel --out-dir ./dist/demo/ ./example/index.html", 9 | "build": "npm-run-all -s clean build:demo", 10 | "build:demo": "cross-env NODE_ENV=production parcel build ./example/index.html --out-dir ./dist/demo/ --public-url /overlapping-avoided-tooltip", 11 | "clean": "rimraf ./dist/*" 12 | }, 13 | "peerDependencies": { 14 | "leaflet": "^1.3.4" 15 | }, 16 | "devDependencies": { 17 | "@types/leaflet": "^1.2.11", 18 | "babel-core": "^6.26.3", 19 | "babel-eslint": "^9.0.0", 20 | "babel-plugin-transform-runtime": "^6.23.0", 21 | "babel-preset-env": "^1.7.0", 22 | "babel-preset-stage-2": "^6.24.1", 23 | "cross-env": "^5.2.0", 24 | "eslint": "^5.6.0", 25 | "npm-run-all": "^4.1.3", 26 | "parcel-bundler": "^1.9.7", 27 | "prettier": "^1.14.2", 28 | "rimraf": "^2.6.2" 29 | }, 30 | "dependencies": { 31 | "leaflet": "^1.3.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: true 4 | }; 5 | --------------------------------------------------------------------------------