├── .eslintrc ├── .gitignore ├── Gruntfile.js ├── README.md ├── TODO ├── dist ├── leaflet-search.min.css ├── leaflet-search.min.js ├── leaflet-search.mobile.min.css ├── leaflet-search.mobile.src.css ├── leaflet-search.src.css └── leaflet-search.src.js ├── examples ├── ajax-jquery.html ├── ajax.html ├── custom-source-data.html ├── custom-tip.html ├── data │ ├── bar.geojson.js │ ├── cities15000.json │ ├── cities15000.raw.txt │ ├── colors.js │ ├── csv2json.php │ ├── custom-icon.png │ ├── htmlcolors.txt │ ├── pharmacy.geojson.js │ ├── restaurant.geojson.js │ ├── restaurant500.geojson.js │ └── us-states.js ├── fuzzy.html ├── geocoder.html ├── geocoding-google.html ├── geocoding-nominatim.html ├── geojson-layer.html ├── jsonp-filtered.html ├── jsonp.html ├── location-url.html ├── methods.html ├── mobile.css ├── mobile.html ├── multiple-layers.html ├── multiple-results.html ├── nominatim.html ├── outside.html ├── popup.html ├── requirejs.html ├── search.php ├── simple.html ├── style.css └── tests.html ├── images ├── back.png ├── favicon.png ├── leaflet-search.jpg ├── loader.gif ├── search-icon-mobile.png └── search-icon.png ├── index.html ├── license.txt ├── package.json └── src ├── leaflet-search-geocoder.js ├── leaflet-search.css ├── leaflet-search.js └── leaflet-search.mobile.css /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2020, 8 | "sourceType": "script" 9 | }, 10 | "plugins": [ 11 | "import", 12 | "node", 13 | "promise" 14 | ], 15 | "rules": { 16 | "import/no-unresolved": [ 17 | 2, 18 | { 19 | "commonjs": true 20 | } 21 | ], 22 | "import/named": 2, 23 | "import/default": 2, 24 | "import/namespace": 2, 25 | "import/no-restricted-paths": 0, 26 | "import/no-absolute-path": 2, 27 | "import/no-dynamic-require": 0, 28 | "import/no-internal-modules": 0, 29 | "import/no-webpack-loader-syntax": 2, 30 | "import/no-self-import": 2, 31 | "import/no-cycle": 2, 32 | "import/no-useless-path-segments": 2, 33 | "import/no-relative-parent-imports": 2, 34 | "import/no-unused-modules": 0, 35 | "import/export": 2, 36 | "import/no-named-as-default": 1, 37 | "import/no-named-as-default-member": 1, 38 | "import/no-deprecated": 0, 39 | "import/no-extraneous-dependencies": [ 40 | 2, 41 | { 42 | "devDependencies": false, 43 | "optionalDependencies": false, 44 | "peerDependencies": false, 45 | "bundledDependencies": false 46 | } 47 | ], 48 | "import/no-mutable-exports": 2, 49 | "import/unambiguous": 0, 50 | "import/no-amd": 2, 51 | "import/no-nodejs-modules": 0, 52 | "import/first": 2, 53 | "import/exports-last": 2, 54 | "import/no-duplicates": 2, 55 | "import/no-namespace": 2, 56 | "import/extensions": 0, 57 | "import/order": 1, 58 | "import/newline-after-import": 2, 59 | "import/prefer-default-export": 1, 60 | "import/max-dependencies": [0], 61 | "import/no-unassigned-import": 1, 62 | "import/no-named-default": 0, 63 | "import/no-default-export": 0, 64 | "import/no-named-export": 0, 65 | "import/no-anonymous-default-export": 0, 66 | "import/group-exports": 2, 67 | "dynamic-import-chunkname": 0, 68 | "comma-dangle": [ 69 | 2, 70 | "never" 71 | ], 72 | "no-cond-assign": [ 73 | 2, 74 | "always" 75 | ], 76 | "no-console": 1, 77 | "no-constant-condition": 2, 78 | "no-control-regex": 2, 79 | "no-debugger": 2, 80 | "no-dupe-args": 2, 81 | "no-dupe-keys": 2, 82 | "no-duplicate-case": 2, 83 | "no-empty-character-class": 2, 84 | "no-empty": 2, 85 | "no-ex-assign": 1, 86 | "no-extra-boolean-cast": 1, 87 | "no-extra-parens": [ 88 | 2, 89 | "functions" 90 | ], 91 | "no-extra-semi": 2, 92 | "no-func-assign": 1, 93 | "no-inner-declarations": [ 94 | 2, 95 | "both" 96 | ], 97 | "no-invalid-regexp": 2, 98 | "no-irregular-whitespace": 2, 99 | "no-negated-in-lhs": 2, 100 | "no-obj-calls": 2, 101 | "no-regex-spaces": 2, 102 | "no-sparse-arrays": 2, 103 | "no-unexpected-multiline": 2, 104 | "no-unreachable": 2, 105 | "use-isnan": 2, 106 | "valid-typeof": 2, 107 | "accessor-pairs": 2, 108 | "block-scoped-var": 2, 109 | "consistent-return": 0, 110 | "curly": [ 111 | 2, 112 | "all" 113 | ], 114 | "default-case": 2, 115 | "dot-location": [ 116 | 2, 117 | "property" 118 | ], 119 | "dot-notation": 2, 120 | "eqeqeq": [ 121 | 2, 122 | "always", {"null": "ignore"} 123 | ], 124 | "guard-for-in": 1, 125 | "no-alert": 1, 126 | "no-caller": 2, 127 | "no-case-declarations": 0, 128 | "no-div-regex": 2, 129 | "no-else-return": 2, 130 | "no-labels": 2, 131 | "no-empty-pattern": 2, 132 | "no-eval": 2, 133 | "no-extend-native": 2, 134 | "no-extra-bind": 2, 135 | "no-fallthrough": 2, 136 | "no-floating-decimal": 2, 137 | "no-implicit-coercion": [2, 138 | { 139 | "boolean": true, 140 | "number": true, 141 | "string": true 142 | } 143 | ], 144 | "no-implied-eval": 2, 145 | "no-invalid-this": 2, 146 | "no-iterator": 2, 147 | "no-lone-blocks": 2, 148 | "no-loop-func": 2, 149 | "no-magic-numbers": 0, 150 | "no-multi-spaces": 2, 151 | "no-multi-str": 2, 152 | "no-native-reassign": 2, 153 | "no-new-func": 2, 154 | "no-new-wrappers": 2, 155 | "no-new": 2, 156 | "no-octal-escape": 2, 157 | "no-octal": 2, 158 | "no-param-reassign": 2, 159 | "no-process-env": 0, 160 | "no-proto": 2, 161 | "no-redeclare": [2, { 162 | "builtinGlobals": true 163 | } 164 | ], 165 | "no-return-assign": 2, 166 | "no-script-url": 2, 167 | "no-self-compare": 2, 168 | "no-sequences": 2, 169 | "no-throw-literal": 1, 170 | "no-unused-expressions": 2, 171 | "no-useless-call": 2, 172 | "no-useless-concat": 2, 173 | "no-void": 2, 174 | "no-with": 2, 175 | "radix": 2, 176 | "vars-on-top": 2, 177 | "wrap-iife": [ 178 | 2, 179 | "outside" 180 | ], 181 | "yoda": 2, 182 | "strict": [ 183 | 2, 184 | "safe" 185 | ], 186 | "no-catch-shadow": 2, 187 | "no-delete-var": 2, 188 | "no-label-var": 2, 189 | "no-shadow-restricted-names": 2, 190 | "no-shadow": [ 191 | 2, 192 | { 193 | "builtinGlobals": true 194 | } 195 | ], 196 | "no-undef-init": 2, 197 | "no-undef": 2, 198 | "no-unused-vars": [ 199 | 2, 200 | { 201 | "vars": "all", 202 | "args": "after-used" 203 | } 204 | ], 205 | "no-use-before-define": 2, 206 | "callback-return": ["error", ["done", "cb"]], 207 | "handle-callback-err": 2, 208 | "no-new-require": 2, 209 | "no-path-concat": 2, 210 | "no-process-exit": 1, 211 | "no-sync": 1, 212 | "array-bracket-spacing": [ 213 | 2, 214 | "never" 215 | ], 216 | "block-spacing": [ 217 | 2, 218 | "always" 219 | ], 220 | "brace-style": [ 221 | 2, 222 | "1tbs", 223 | { 224 | "allowSingleLine": false 225 | } 226 | ], 227 | "camelcase": [ 228 | 2, 229 | { 230 | "properties": "always" 231 | } 232 | ], 233 | "comma-spacing": [ 234 | 2, 235 | { 236 | "before": false, 237 | "after": true 238 | } 239 | ], 240 | "comma-style": [ 241 | 2, 242 | "first", 243 | { 244 | "exceptions": { 245 | "ArrayExpression": true, 246 | "ArrayPattern": true, 247 | "ArrowFunctionExpression": true, 248 | "CallExpression": true, 249 | "FunctionDeclaration": true, 250 | "FunctionExpression": true, 251 | "ImportDeclaration": true, 252 | "ObjectExpression": true, 253 | "ObjectPattern": true, 254 | "NewExpression": true 255 | } 256 | } 257 | ], 258 | "computed-property-spacing": [ 259 | 2, 260 | "never" 261 | ], 262 | "consistent-this": [ 263 | 2, 264 | "that" 265 | ], 266 | "eol-last": 2, 267 | "func-names": 1, 268 | "func-style": [ 269 | 1, 270 | "expression", 271 | { 272 | "allowArrowFunctions": true 273 | } 274 | ], 275 | "id-length": 1, 276 | "key-spacing": [ 277 | 2, 278 | { 279 | "beforeColon": false, 280 | "afterColon": true 281 | } 282 | ], 283 | "linebreak-style": [ 284 | 2, 285 | "unix" 286 | ], 287 | "new-cap": 2, 288 | "new-parens": 2, 289 | "newline-after-var": [ 290 | 2, 291 | "always" 292 | ], 293 | "no-array-constructor": 2, 294 | "no-bitwise": 2, 295 | "no-continue": 2, 296 | "no-lonely-if": 2, 297 | "no-mixed-spaces-and-tabs": 2, 298 | "no-multiple-empty-lines": [ 299 | 1, 300 | { 301 | "max": 2, 302 | "maxEOF": 1 303 | } 304 | ], 305 | "no-negated-condition": 1, 306 | "no-nested-ternary": 2, 307 | "no-new-object": 2, 308 | "no-plusplus": 2, 309 | "no-spaced-func": 2, 310 | "no-ternary": 0, 311 | "no-trailing-spaces": [ 312 | 2, 313 | { 314 | "skipBlankLines": false 315 | } 316 | ], 317 | "no-underscore-dangle": 2, 318 | "no-unneeded-ternary": 2, 319 | "object-curly-spacing": [ 320 | 2, 321 | "never" 322 | ], 323 | "one-var": [ 324 | 2, 325 | { 326 | "var": "always", 327 | "let": "always" 328 | } 329 | ], 330 | "operator-assignment": [ 331 | 2, 332 | "always" 333 | ], 334 | "operator-linebreak": [ 335 | 2, 336 | "after" 337 | ], 338 | "quote-props": 2, 339 | "quotes": [ 340 | 2, 341 | "single", 342 | "avoid-escape" 343 | ], 344 | "semi-spacing": 2, 345 | "semi": [ 346 | 2, 347 | "always" 348 | ], 349 | "keyword-spacing": [ 350 | 2, 351 | { 352 | "before": true, 353 | "after": true 354 | } 355 | ], 356 | "space-before-blocks": [ 357 | 2, 358 | "always" 359 | ], 360 | "space-before-function-paren": [ 361 | 2, 362 | "never" 363 | ], 364 | "space-in-parens": [ 365 | 2, 366 | "never" 367 | ], 368 | "space-infix-ops": 2, 369 | "space-unary-ops": [ 370 | 2, 371 | { 372 | "words": true, 373 | "nonwords": false 374 | } 375 | ], 376 | "wrap-regex": 2, 377 | "arrow-parens": [ 378 | 2, 379 | "as-needed" 380 | ], 381 | "arrow-spacing": [ 382 | 2, 383 | { 384 | "before": true, 385 | "after": true 386 | } 387 | ], 388 | "constructor-super": 2, 389 | "generator-star-spacing": [ 390 | 2, 391 | { 392 | "before": false, 393 | "after": true 394 | } 395 | ], 396 | "no-confusing-arrow": 1, 397 | "no-class-assign": 2, 398 | "no-const-assign": 2, 399 | "no-dupe-class-members": 1, 400 | "no-this-before-super": 2, 401 | "no-var": 1, 402 | "object-shorthand": [ 403 | 2, 404 | "always" 405 | ], 406 | "prefer-arrow-callback": 1, 407 | "prefer-const": 1, 408 | "prefer-spread": 1, 409 | "prefer-template": 2, 410 | "require-yield": 2 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | packages/* 3 | node_modules/* 4 | npm-debug.log 5 | package-lock.json -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.loadNpmTasks('grunt-contrib-uglify'); 6 | grunt.loadNpmTasks('grunt-contrib-concat'); 7 | grunt.loadNpmTasks('grunt-contrib-clean'); 8 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 9 | grunt.loadNpmTasks('grunt-contrib-jshint'); 10 | grunt.loadNpmTasks("grunt-remove-logging"); 11 | grunt.loadNpmTasks('grunt-contrib-watch'); 12 | grunt.loadNpmTasks('grunt-todos'); 13 | 14 | grunt.initConfig({ 15 | pkg: grunt.file.readJSON('package.json'), 16 | meta: { 17 | banner: 18 | '/* \n'+ 19 | ' * Leaflet Control Search v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n'+ 20 | ' * \n'+ 21 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> \n'+ 22 | ' * <%= pkg.author.email %> \n'+ 23 | ' * <%= pkg.author.url %> \n'+ 24 | ' * \n'+ 25 | ' * Licensed under the <%= pkg.license %> license. \n'+ 26 | ' * \n'+ 27 | ' * Demo: \n'+ 28 | ' * <%= pkg.homepage %> \n'+ 29 | ' * \n'+ 30 | ' * Source: \n'+ 31 | ' * <%= pkg.repository.url %> \n'+ 32 | ' * \n'+ 33 | ' */\n' 34 | }, 35 | clean: { 36 | dist: { 37 | src: ['dist/*'] 38 | } 39 | }, 40 | removelogging: { 41 | dist: { 42 | src: 'dist/*.js' 43 | } 44 | }, 45 | jshint: { 46 | options: { 47 | esversion: 6, 48 | globals: { 49 | 'no-console': true, 50 | module: true 51 | }, 52 | '-W099': true, 53 | '-W033': true, 54 | '-W044': true, 55 | '-W104': true, 56 | }, 57 | files: ['src/*.js'] 58 | }, 59 | concat: { 60 | options: { 61 | banner: '<%= meta.banner %>' 62 | }, 63 | dist: { 64 | files: { 65 | 'dist/leaflet-search.src.js': ['src/leaflet-search.js'], 66 | 'dist/leaflet-search.src.css': ['src/leaflet-search.css'], 67 | 'dist/leaflet-search.mobile.src.css': ['src/leaflet-search.mobile.css'] 68 | } 69 | } 70 | }, 71 | uglify: { 72 | dist: { 73 | files: { 74 | 'dist/leaflet-search.min.js': ['dist/leaflet-search.src.js'] 75 | } 76 | } 77 | }, 78 | cssmin: { 79 | combine: { 80 | files: { 81 | 'dist/leaflet-search.min.css': ['src/leaflet-search.css'], 82 | 'dist/leaflet-search.mobile.min.css': ['src/leaflet-search.mobile.css'] 83 | } 84 | }, 85 | options: { 86 | banner: '<%= meta.banner %>' 87 | }, 88 | minify: { 89 | expand: true, 90 | cwd: 'dist/', 91 | files: { 92 | 'dist/leaflet-search.min.css': ['src/leaflet-search.css'], 93 | 'dist/leaflet-search.mobile.min.css': ['src/leaflet-search.mobile.css'] 94 | } 95 | } 96 | }, 97 | watch: { 98 | dist: { 99 | options: { livereload: true }, 100 | files: ['src/*'], 101 | tasks: ['clean','concat','cssmin','jshint'] 102 | } 103 | } 104 | }); 105 | 106 | grunt.registerTask('default', [ 107 | 'clean', 108 | 'concat', 109 | 'cssmin', 110 | 'removelogging', 111 | 'jshint', 112 | 'uglify' 113 | ]); 114 | 115 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaflet Control Search 2 | ============ 3 | 4 | [![npm version](https://badge.fury.io/js/leaflet-search.svg)](http://badge.fury.io/js/leaflet-search) 5 | 6 | A Leaflet control that search markers/features location by custom property.
7 | Support ajax/jsonp autocompletion and JSON data filter/remapping. 8 | 9 | *Licensed under the MIT license.* 10 | 11 | *Copyright [Stefano Cudini](https://opengeo.tech/stefano-cudini/)* 12 | 13 | If this project helped your work help me to keep this alive by [Paypal **DONATION ❤**](https://www.paypal.me/stefanocudini) 14 | 15 | Tested in Leaflet 0.7.x and 1.3.x 16 | 17 | ![Image](https://raw.githubusercontent.com/stefanocudini/leaflet-search/master/images/leaflet-search.jpg) 18 | 19 | # Where 20 | 21 | **Demo:** 22 | [opengeo.tech/maps/leaflet-search](https://opengeo.tech/maps/leaflet-search/) 23 | 24 | **Source code:** 25 | [Github](https://github.com/stefanocudini/leaflet-search) 26 | [NPM](https://npmjs.org/package/leaflet-search) 27 | 28 | # Install 29 | ``` 30 | npm install --save leaflet-search 31 | ``` 32 | # Options 33 | | Option | Default | Description | 34 | | --------------- | -------- | ----------------------------------------- | 35 | | url | '' | url for search by ajax request, ex: "search.php?q={s}". Can be function to returns string for dynamic parameter setting | | 36 | | layer | null | layer where search markers(is a L.LayerGroup) | 37 | | sourceData | null | function to fill _recordsCache, passed searching text by first param and callback in second | 38 | | jsonpParam | null | jsonp param name for search by jsonp service, ex: "callback" | 39 | | propertyLoc | 'loc' | field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) support dotted format: 'prop.subprop.title' | 40 | | propertyName | 'title' | property in marker.options(or feature.properties for vector layer) trough filter elements in layer, | 41 | | formatData | null | callback for reformat all data from source to indexed data object | 42 | | filterData | null | callback for filtering data from text searched, params: textSearch, allRecords | 43 | | moveToLocation | null | callback run on location found, params: latlng, title, map | 44 | | buildTip | null | function to return row tip html node(or html string), receive text tooltip in first param | 45 | | container | '' | container id to insert Search Control | 46 | | zoom | null | default zoom level for move to location | 47 | | minLength | 1 | minimal text length for autocomplete | 48 | | initial | true | search elements only by initial text | 49 | | casesensitive | false | search elements in case sensitive text | 50 | | autoType | true | complete input with first suggested result and select this filled-in text. | 51 | | delayType | 400 | delay while typing for show tooltip | 52 | | tooltipLimit | -1 | limit max results to show in tooltip. -1 for no limit, 0 for no results | 53 | | tipAutoSubmit | true | auto map panTo when click on tooltip | 54 | | firstTipSubmit | false | auto select first result con enter click | 55 | | autoResize | true | autoresize on input change | 56 | | collapsed | true | collapse search control at startup | 57 | | autoCollapse | false | collapse search control after submit(on button or on tips if enabled tipAutoSubmit) | 58 | | autoCollapseTime| 1200 | delay for autoclosing alert and collapse after blur | 59 | | textErr | 'Location not found' | error message | 60 | | textCancel | 'Cancel | title in cancel button | 61 | | textPlaceholder | 'Search' | placeholder value | 62 | | hideMarkerOnCollapse | false | remove circle and marker on search control collapsed | 63 | | position | 'topleft'| position in the map | 64 | | marker | {} | custom L.Marker or false for hide | 65 | | marker.icon | false | custom L.Icon for maker location or false for hide | 66 | | marker.animate | true | animate a circle over location found | 67 | | marker.circle | L.CircleMarker options | draw a circle in location found | 68 | 69 | # Events 70 | | Event | Data | Description | 71 | | ---------------------- | ---------------------- | ----------------------------------------- | 72 | | 'search:locationfound' | {latlng, title, layer} | fired after moved and show markerLocation | 73 | | 'search:expanded' | {} | fired after control was expanded | 74 | | 'search:collapsed' | {} | fired after control was collapsed | 75 | 76 | # Methods 77 | | Method | Arguments | Description | 78 | | --------------------- | ---------------------- | ---------------------------- | 79 | | setLayer() | L.LayerGroup() | set layer search at runtime | 80 | | showAlert() | 'Text message' | show alert message | 81 | | searchText() | 'Text searched' | search text by external code | 82 | 83 | 84 | # Examples 85 | (require src/leaflet-search.css) 86 | 87 | Adding the search control to the map: 88 | ```javascript 89 | var searchLayer = L.layerGroup().addTo(map); 90 | //... adding data in searchLayer ... 91 | map.addControl( new L.Control.Search({layer: searchLayer}) ); 92 | //searchLayer is a L.LayerGroup contains searched markers 93 | ``` 94 | 95 | Short way: 96 | ```javascript 97 | var searchLayer = L.geoJson().addTo(map); 98 | //... adding data in searchLayer ... 99 | L.map('map', { searchControl: {layer: searchLayer} }); 100 | ``` 101 | 102 | AMD module: 103 | ```javascript 104 | require(["leaflet", "leafletSearch"],function(L, LeafletSearch) { 105 | 106 | //... initialize leaflet map and dataLayer ... 107 | 108 | map.addControl( new LeafletSearch({ 109 | layer: dataLayer 110 | }) ); 111 | }); 112 | ``` 113 | 114 | # Build 115 | 116 | Therefore the deployment require **npm** installed in your system. 117 | ```bash 118 | npm install 119 | npx grunt 120 | ``` 121 | 122 | 123 | # Use Cases 124 | This list is intended to be of utility for all developers who are looking web mapping sample code to solve complex problems of integration with other systems using Leaflet Control Search. 125 | 126 | **Anyone can add the link of your website** 127 | 128 | *(spamming urls will be automatically deleted)* 129 | 130 | * [demography colorado.gov](https://demography.dola.colorado.gov/CensusAPI_Map/?lat=39&lng=-104.8&z=9&s=50&v=mhi&sn=jenks&cs=mh1&cl=7) 131 | * [NMEA Generator](https://nmeagen.org/) 132 | * [Guihuayun maps](http://guihuayun.com/maps/map_frame.php) 133 | * [Pouemes](http://pouemes.free.fr) 134 | * [Folium](https://github.com/python-visualization/folium) 135 | * [OpenTopoMap](https://opentopomap.org/) 136 | * [Falesia.it](https://www.falesia.it/it/map/169729/260158/2/FAL/title=Mondo.html) 137 | * [Leaflet Control Search (Official demos)](https://opengeo.tech/maps/leaflet-search/) 138 | * [Parkowanie Gliwice](http://parkowaniegliwice.pl/lista-parkomatow/) 139 | * [Agenziauscite.Openstreetmap.it](http://agenziauscite.openstreetmap.it/compare.html) 140 | * [Modern Leaflet Toolbar](https://getbounds.com/blog/a-modern-leaflet-toolbar/) 141 | * [UnGiro.it](http://ungiro.it/percorsi/ciclomuro-street-art-bike-tour.htm) 142 | * [OpenBeerMap](http://openbeermap.github.io/) 143 | * [Spatial statistics for the city of Tampere, Finland](https://github.com/ernoma/GeoStatTampere) 144 | * [BALIMIO Bali photo guide](http://balimio.com/map) 145 | * [Flask Admin](https://github.com/flask-admin/flask-admin) 146 | * [LIVE-Map LiF:YO](http://lif-tools.com/) 147 | * [OpenTrailMap](http://michaelskaug.com/projects/OpenTrailMap/) 148 | * [Rutas Morelia](https://www.rutasmorelia.com/) 149 | * [EDSM - Galactic Map](https://www.edsm.net/en/galactic-mapping) 150 | * [The area of effect of the "MOAB" bomb in Afghanistan](https://www.dhkconsulting.com/moab/moab.html) 151 | * [Ipyleaflet](https://github.com/jupyter-widgets/ipyleaflet) 152 | * [Craft Ottawa](https://craftottawa.ca) 153 | * [Sea Florida Change](https://www.seaflchange.org/) 154 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Tasks found in: src/leaflet-search-geocoder.js 2 | [Line: 61] [low] //TODO refact resp data 3 | Tasks found in: src/leaflet-search.js 4 | [Line: 16] [low] //TODO implement can do research on multiple sources layers and remote 5 | [Line: 17] [low] //TODO history: false, //show latest searches in tooltip 6 | [Line: 21] [low] //TODO here insert function search inputText FIRST in _recordsCache keys and if not find results.. 7 | [Line: 24] [low] //TODO change structure of _recordsCache 8 | [Line: 27] [low] //TODO important optimization!!! always append data in this._recordsCache 9 | [Line: 31] [low] //TODO here insert function search inputText FIRST in _recordsCache keys and if not find results.. 10 | [Line: 34] [low] //TODO change structure of _recordsCache 11 | [Line: 62] [low] //TODO implements uniq option 'sourceData' to recognizes source type: url,array,callback or layer 12 | [Line: 501] [low] //TODO throw new Error("propertyName '"+propName+"' not found in JSON data"); 13 | [Line: 510] [low] //TODO add rnd param or randomize callback name! in recordsFromJsonp 14 | [Line: 531] [low] //TODO add rnd param or randomize callback name! in recordsFromAjax 15 | [Line: 635] [low] //TODO implements autype without selection(useful for mobile device) 16 | [Line: 766] [low] //TODO _recordsFromLayer must return array of objects, formatted from _formatData 17 | [Line: 787] [low] //TODO refact! 18 | [Line: 979] [low] //TODO refact animate() more smooth! like this: http://goo.gl/DDlRs 19 | [Line: 1003] [low] //TODO use create event 'animateEnd' in L.Control.Search.Marker 20 | [Line: 18] [med] //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location 21 | [Line: 19] [med] //FIXME option condition problem {autoCollapse: false } 22 | [Line: 910] [med] //FIXME autoCollapse option hide self._markerSearch before visualized!! 23 | -------------------------------------------------------------------------------- /dist/leaflet-search.min.css: -------------------------------------------------------------------------------- 1 | .leaflet-container .leaflet-control-search{position:relative;float:left;background:#fff;color:#1978cf;border:2px solid rgba(0,0,0,.2);background-clip:padding-box;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;background-color:rgba(255,255,255,.8);z-index:1000;margin-left:10px;margin-top:10px}.leaflet-control-search.search-exp{background:#fff;border:2px solid rgba(0,0,0,.2);background-clip:padding-box}.leaflet-control-search .search-input{display:block;float:left;background:#fff;border:1px solid #666;border-radius:2px;height:22px;padding:0 20px 0 2px;margin:4px 0 4px 4px}.leaflet-control-search.search-load .search-input{background:url('../images/loader.gif') no-repeat center right #fff}.leaflet-control-search.search-load .search-cancel{visibility:hidden}.leaflet-control-search .search-cancel{display:block;width:22px;height:22px;position:absolute;right:28px;margin:6px 0;background:url('../images/search-icon.png') no-repeat 0 -46px;text-decoration:none;opacity:.8}.leaflet-control-search .search-cancel:hover{opacity:1}.leaflet-control-search .search-cancel span{display:none;font-size:18px;line-height:20px;color:#ccc;font-weight:700}.leaflet-control-search .search-cancel:hover span{color:#aaa}.leaflet-control-search .search-button{display:block;float:left;width:30px;height:30px;background:url('../images/search-icon.png') no-repeat 4px 4px #fff;border-radius:4px}.leaflet-control-search .search-button:hover{background:url('../images/search-icon.png') no-repeat 4px -20px #fafafa}.leaflet-control-search .search-tooltip{position:absolute;top:100%;left:0;float:left;list-style:none;padding-left:0;min-width:120px;max-height:122px;box-shadow:1px 1px 6px rgba(0,0,0,.4);background-color:rgba(0,0,0,.25);z-index:1010;overflow-y:auto;overflow-x:hidden;cursor:pointer}.leaflet-control-search .search-tip{margin:2px;padding:2px 4px;display:block;color:#000;background:#eee;border-radius:.25em;text-decoration:none;white-space:nowrap;vertical-align:middle}.leaflet-control-search .search-button:hover{background-color:#f4f4f4}.leaflet-control-search .search-tip-select,.leaflet-control-search .search-tip:hover{background-color:#fff}.leaflet-control-search .search-alert{cursor:pointer;clear:both;font-size:.75em;margin-bottom:5px;padding:0 .25em;color:#e00;font-weight:700;border-radius:.25em} -------------------------------------------------------------------------------- /dist/leaflet-search.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("function"==typeof define&&define.amd)define(["leaflet"],t);else if("undefined"!=typeof module)module.exports=t(require("leaflet"));else{if(void 0===window.L)throw new Error("Leaflet must be loaded first");t(window.L)}}(function(a){return a.Control.Search=a.Control.extend({includes:"1"===a.version[0]?a.Evented.prototype:a.Mixin.Events,options:{url:"",layer:null,sourceData:null,jsonpParam:null,propertyLoc:"loc",propertyName:"title",formatData:null,filterData:null,moveToLocation:null,buildTip:null,container:"",zoom:null,minLength:1,initial:!0,casesensitive:!1,autoType:!0,delayType:400,tooltipLimit:-1,tipAutoSubmit:!0,firstTipSubmit:!1,autoResize:!0,collapsed:!0,autoCollapse:!1,autoCollapseTime:1200,textErr:"Location not found",textCancel:"Cancel",textPlaceholder:"Search...",hideMarkerOnCollapse:!1,position:"topleft",marker:{icon:!1,animate:!0,circle:{radius:10,weight:3,color:"#e03",stroke:!0,fill:!1}}},_getPath:function(t,e){var i=e.split("."),e=i.pop(),o=i.length;let s=i[0],n=1;if(0⊗",a.DomEvent.on(e,"click",a.DomEvent.stop,this).on(e,"click",this.cancel,this),e},_createButton:function(t,e){e=a.DomUtil.create("a",e,this._container);return e.href="#",e.title=t,a.DomEvent.on(e,"click",a.DomEvent.stop,this).on(e,"click",this._handleSubmit,this).on(e,"focus",this.collapseDelayedStop,this).on(e,"blur",this.collapseDelayed,this),e},_createTooltip:function(t){const e=this;t=a.DomUtil.create("ul",t,this._container);return t.style.display="none",a.DomEvent.disableClickPropagation(t).on(t,"blur",this.collapseDelayed,this).on(t,"wheel",function(t){e.collapseDelayedStop(),a.DomEvent.stopPropagation(t)},this).on(t,"mouseover",function(t){e.collapseDelayedStop()},this),t},_createTip:function(e,t){let i;return this.options.buildTip?"string"==typeof(i=this.options.buildTip.call(this,e,t))&&((t=a.DomUtil.create("div")).innerHTML=i,i=t.firstChild):(i=a.DomUtil.create("li","")).innerHTML=e,a.DomUtil.addClass(i,"search-tip"),i._text=e,this.options.tipAutoSubmit&&a.DomEvent.disableClickPropagation(i).on(i,"click",a.DomEvent.stop,this).on(i,"click",function(t){this._input.value=e,this._handleAutoresize(),this._input.focus(),this._hideTooltip(),this._handleSubmit()},this),i},_getUrl:function(t){return"function"==typeof this.options.url?this.options.url(t):this.options.url},_defaultFilterData:function(t,e){var i={};if(""===(t=t.replace(new RegExp("[.*+?^${}()|[]\\]","g"),"")))return[];var o=this.options.initial?"^":"",s=this.options.casesensitive?void 0:"i",n=new RegExp(o+t,s);for(const r in e)n.test(r)&&(i[r]=e[r]);return i},showTooltip:function(t){if(this._countertips=0,this._tooltip.innerHTML="",this._tooltip.currentSelection=-1,this.options.tooltipLimit)for(const e in t){if(this._countertips===this.options.tooltipLimit)break;this._countertips++,this._tooltip.appendChild(this._createTip(e,t[e]))}return 0=this.options.minLength?(clearTimeout(this.timerKeypress),this.timerKeypress=setTimeout(function(){e._fillRecordsCache()},this.options.delayType)):this._hideTooltip()}this._handleAutoresize()},searchText:function(t){var e=t.charCodeAt(t.length);this._input.value=t,this._input.style.display="block",a.DomUtil.addClass(this._container,"search-exp"),this._autoTypeTmp=!1,this._handleKeypress({keyCode:e})},_fillRecordsCache:function(){const e=this;var t=this._input.value;let i;this._curReq&&this._curReq.abort&&this._curReq.abort(),a.DomUtil.addClass(this._container,"search-load"),this.options.layer?(this._recordsCache=this._recordsFromLayer(),i=this._filterData(this._input.value,this._recordsCache),this.showTooltip(i),a.DomUtil.removeClass(this._container,"search-load")):(this.options.sourceData?this._retrieveData=this.options.sourceData:this.options.url&&(this._retrieveData=this.options.jsonpParam?this._recordsFromJsonp:this._recordsFromAjax),this._curReq=this._retrieveData.call(this,t,function(t){e._recordsCache=e._formatData(e,t),i=e.options.sourceData?e._filterData(e._input.value,e._recordsCache):e._recordsCache,e.showTooltip(i),a.DomUtil.removeClass(e._container,"search-load")}))},_handleAutoresize:function(){var t;this._input.style.maxWidth!==this._map._container.offsetWidth&&(t=this._map._container.clientWidth,this._input.style.maxWidth=(t-=83).toString()+"px"),this.options.autoResize&&this._container.offsetWidth+20=e.length-1?a.DomUtil.addClass(e[this._tooltip.currentSelection],"search-tip-select"):-1===t&&this._tooltip.currentSelection<=0?this._tooltip.currentSelection=-1:"none"!==this._tooltip.style.display&&(this._tooltip.currentSelection+=t,a.DomUtil.addClass(e[this._tooltip.currentSelection],"search-tip-select"),this._input.value=e[this._tooltip.currentSelection]._text,(t=e[this._tooltip.currentSelection].offsetTop)+e[this._tooltip.currentSelection].clientHeight>=this._tooltip.scrollTop+this._tooltip.clientHeight?this._tooltip.scrollTop=t-this._tooltip.clientHeight+e[this._tooltip.currentSelection].clientHeight:t<=this._tooltip.scrollTop&&(this._tooltip.scrollTop=t))},_handleSubmit:function(){var t;this._hideAutoType(),this.hideAlert(),this._hideTooltip(),"none"===this._input.style.display?this.expand():""===this._input.value?this.collapse():(t=this._getLocation(this._input.value))?(this.showLocation(t,this._input.value),this.fire("search:locationfound",{latlng:t,text:this._input.value,layer:t.layer||null})):this.showAlert()},_getLocation:function(t){return!!Object.prototype.hasOwnProperty.call(this._recordsCache,t)&&this._recordsCache[t]},_defaultMoveToLocation:function(t,e,i){this.options.zoom?this._map.setView(t,this.options.zoom):this._map.panTo(t)},showLocation:function(e,t){const i=this;return i._map.once("moveend zoomend",function(t){i._markerSearch&&i._markerSearch.addTo(i._map).setLatLng(e)}),i._moveToLocation(e,t,i._map),i.options.autoCollapse&&i.collapse(),i}}),a.Control.Search.Marker=a.Marker.extend({includes:"1"===a.version[0]?a.Evented.prototype:a.Mixin.Events,options:{icon:new a.Icon.Default,animate:!0,circle:{radius:10,weight:3,color:"#e03",stroke:!0,fill:!1}},initialize:function(t,e){a.setOptions(this,e),!0===e.icon&&(e.icon=new a.Icon.Default),a.Marker.prototype.initialize.call(this,t,e),a.Control.Search.prototype._isObject(this.options.circle)&&(this._circleLoc=new a.CircleMarker(t,this.options.circle))},onAdd:function(t){a.Marker.prototype.onAdd.call(this,t),this._circleLoc&&(t.addLayer(this._circleLoc),this.options.animate)&&this.animate()},onRemove:function(t){a.Marker.prototype.onRemove.call(this,t),this._circleLoc&&t.removeLayer(this._circleLoc)},setLatLng:function(t){return a.Marker.prototype.setLatLng.call(this,t),this._circleLoc&&this._circleLoc.setLatLng(t),this},_initIcon:function(){this.options.icon&&a.Marker.prototype._initIcon.call(this)},_removeIcon:function(){this.options.icon&&a.Marker.prototype._removeIcon.call(this)},animate:function(){if(this._circleLoc){const o=this._circleLoc;let t=parseInt(o._radius/5);const s=this.options.circle.radius;let e=2*o._radius,i=0;o._timerAnimLoc=setInterval(function(){i+=.5,t+=i,e-=t,o.setRadius(e),e 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Leaflet.Control.Search

15 | 16 |

AJAX Example(by jQuery): search locations by Ajax request

17 |
18 | 19 |
20 | Search values:
21 | black, blue, cyan, darkblue, darkred, darkgray, gray, gree, red, yellow, white 22 |
23 | 24 | 25 | 26 | 27 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/ajax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

AJAX Example: search locations by Ajax request, with custom icon marker

15 |
16 | 17 |
18 | Search values:
19 | black, blue, cyan, darkblue, darkred, darkgray, gray, gree, red, yellow, white 20 |
21 | 22 | 23 | 24 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/custom-source-data.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

Static data from custom data source Example: search locations using option sourceData in static Array

15 |
16 | 17 |
18 | Search values:
19 | black, blue, cyan, darkblue, darkred, darkgray, gray, gree, red, yellow, white 20 |
21 | 22 | 23 | 24 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/custom-tip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

Custom Tip Example: customize each tip in menu

15 |
16 | 17 |
18 | Search values:
19 | aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ... 20 |
21 | 22 | 23 | 24 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/data/bar.geojson.js: -------------------------------------------------------------------------------- 1 | var bar = { 2 | "type": "FeatureCollection", 3 | "generator": "overpass-turbo", 4 | "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.", 5 | "timestamp": "2015-08-08T19:03:02Z", 6 | "features": [ 7 | { 8 | "type": "Feature", 9 | "id": "node/500129236", 10 | "properties": { 11 | "@id": "node/500129236", 12 | "addr:city": "Roma", 13 | "addr:country": "IT", 14 | "addr:housenumber": "5", 15 | "addr:postcode": "00184", 16 | "addr:street": "Piazza della Madonna dei Monti", 17 | "amenity": "bar", 18 | "name": "la Bottega del caffe'" 19 | }, 20 | "geometry": { 21 | "type": "Point", 22 | "coordinates": [ 23 | 12.4910927, 24 | 41.8950196 25 | ] 26 | } 27 | }, 28 | { 29 | "type": "Feature", 30 | "id": "node/560446888", 31 | "properties": { 32 | "@id": "node/560446888", 33 | "amenity": "bar", 34 | "name": "Marani" 35 | }, 36 | "geometry": { 37 | "type": "Point", 38 | "coordinates": [ 39 | 12.5132952, 40 | 41.8970607 41 | ] 42 | } 43 | }, 44 | { 45 | "type": "Feature", 46 | "id": "node/574326909", 47 | "properties": { 48 | "@id": "node/574326909", 49 | "amenity": "bar", 50 | "name": "Il Faraone" 51 | }, 52 | "geometry": { 53 | "type": "Point", 54 | "coordinates": [ 55 | 12.4910603, 56 | 41.8948257 57 | ] 58 | } 59 | }, 60 | { 61 | "type": "Feature", 62 | "id": "node/1213332726", 63 | "properties": { 64 | "@id": "node/1213332726", 65 | "addr:city": "Roma", 66 | "addr:country": "IT", 67 | "addr:housenumber": "139", 68 | "addr:postcode": "00185", 69 | "addr:street": "Via Principe Amedeo", 70 | "amenity": "bar", 71 | "name": "Snackbar Eugenio", 72 | "operator": "Marchetti Eugenio", 73 | "phone": "+39 06 4466154" 74 | }, 75 | "geometry": { 76 | "type": "Point", 77 | "coordinates": [ 78 | 12.5031364, 79 | 41.8970108 80 | ] 81 | } 82 | }, 83 | { 84 | "type": "Feature", 85 | "id": "node/1660369493", 86 | "properties": { 87 | "@id": "node/1660369493", 88 | "addr:city": "Roma", 89 | "addr:country": "IT", 90 | "addr:housenumber": "27", 91 | "addr:street": "Via San Martino della Battaglia", 92 | "amenity": "bar", 93 | "name": "Pretorius Cafe'", 94 | "operator": "Cesarina Utilia", 95 | "phone": "+39 06 490077", 96 | "ref:vatin": "IT09397401002" 97 | }, 98 | "geometry": { 99 | "type": "Point", 100 | "coordinates": [ 101 | 12.5051946, 102 | 41.905864 103 | ] 104 | } 105 | }, 106 | { 107 | "type": "Feature", 108 | "id": "node/1660404822", 109 | "properties": { 110 | "@id": "node/1660404822", 111 | "addr:city": "Roma", 112 | "addr:country": "IT", 113 | "addr:housenumber": "9", 114 | "addr:street": "Via Clementina", 115 | "amenity": "bar", 116 | "cuisine": "regional", 117 | "name": "Casa Clementina", 118 | "operator": "Flexy Soc.Coop Arl.", 119 | "phone": "+39 06 48913254", 120 | "ref:vatin": "IT09253451000", 121 | "restaurant:type": "enoteca" 122 | }, 123 | "geometry": { 124 | "type": "Point", 125 | "coordinates": [ 126 | 12.492714, 127 | 41.8959687 128 | ] 129 | } 130 | }, 131 | { 132 | "type": "Feature", 133 | "id": "node/1926196463", 134 | "properties": { 135 | "@id": "node/1926196463", 136 | "amenity": "bar", 137 | "name": "Twins" 138 | }, 139 | "geometry": { 140 | "type": "Point", 141 | "coordinates": [ 142 | 12.5009894, 143 | 41.8999812 144 | ] 145 | } 146 | }, 147 | { 148 | "type": "Feature", 149 | "id": "node/2092013187", 150 | "properties": { 151 | "@id": "node/2092013187", 152 | "addr:housenumber": "12", 153 | "addr:street": "Via di San Giovanni in Laterano", 154 | "amenity": "bar", 155 | "name": "My Bar", 156 | "operator": "Anfiteatro S.r.l.", 157 | "phone": "+39 06 7004425", 158 | "ref:vatin": "IT067475410008" 159 | }, 160 | "geometry": { 161 | "type": "Point", 162 | "coordinates": [ 163 | 12.4946166, 164 | 41.8898973 165 | ] 166 | } 167 | }, 168 | { 169 | "type": "Feature", 170 | "id": "node/2128214065", 171 | "properties": { 172 | "@id": "node/2128214065", 173 | "addr:city": "Roma", 174 | "addr:country": "IT", 175 | "addr:housenumber": "106", 176 | "addr:postcode": "00184", 177 | "addr:street": "Via di San Giovanni in Laterano", 178 | "amenity": "bar", 179 | "cuisine": "italian", 180 | "name": "San Clemente", 181 | "source": "GPS", 182 | "website": "http://www.iclementini.it/" 183 | }, 184 | "geometry": { 185 | "type": "Point", 186 | "coordinates": [ 187 | 12.4979123, 188 | 41.8889233 189 | ] 190 | } 191 | }, 192 | { 193 | "type": "Feature", 194 | "id": "node/2244964954", 195 | "properties": { 196 | "@id": "node/2244964954", 197 | "addr:housenumber": "40", 198 | "addr:street": "Via Palestro", 199 | "amenity": "bar", 200 | "contact:phone": "+39 06 49382682", 201 | "contact:website": "http://the-yellow.com/", 202 | "name": "The Yellow Bar", 203 | "payment:bitcoin": "yes" 204 | }, 205 | "geometry": { 206 | "type": "Point", 207 | "coordinates": [ 208 | 12.5045633, 209 | 41.9048511 210 | ] 211 | } 212 | }, 213 | { 214 | "type": "Feature", 215 | "id": "node/2361780469", 216 | "properties": { 217 | "@id": "node/2361780469", 218 | "addr:city": "Roma", 219 | "addr:housenumber": "72/73", 220 | "addr:street": "Via Urbana", 221 | "amenity": "bar", 222 | "name": "Er Caffettiere", 223 | "operator": "Sa.Pe. SNC di Spartaco Pelle & C.", 224 | "phone": "+39 320 4168532", 225 | "ref:vatin": "IT09487221005", 226 | "restaurant:type:it": "bar;tavola_calda" 227 | }, 228 | "geometry": { 229 | "type": "Point", 230 | "coordinates": [ 231 | 12.4929144, 232 | 41.8949856 233 | ] 234 | } 235 | }, 236 | { 237 | "type": "Feature", 238 | "id": "node/3217327590", 239 | "properties": { 240 | "@id": "node/3217327590", 241 | "addr:city": "Roma", 242 | "addr:housenumber": "65", 243 | "addr:street": "Via dei Cerchi", 244 | "amenity": "bar", 245 | "name": "0,75", 246 | "opening_hours": "Mo-Su 11:00-02:00", 247 | "phone": "+39 066875706" 248 | }, 249 | "geometry": { 250 | "type": "Point", 251 | "coordinates": [ 252 | 12.4839357, 253 | 41.8879519 254 | ] 255 | } 256 | }, 257 | { 258 | "type": "Feature", 259 | "id": "node/3320437843", 260 | "properties": { 261 | "@id": "node/3320437843", 262 | "addr:housenumber": "26", 263 | "addr:street": "Via Capo d'Africa", 264 | "amenity": "bar", 265 | "name": "La Follia", 266 | "operator": "Dolce Calabria SAS di Morello Alessandra & C.", 267 | "phone": "+39 06 45477438", 268 | "ref:vatin": "IT11279721002", 269 | "restaurant:type:it": "wine_bar", 270 | "shop": "pastry" 271 | }, 272 | "geometry": { 273 | "type": "Point", 274 | "coordinates": [ 275 | 12.4952573, 276 | 41.8888415 277 | ] 278 | } 279 | }, 280 | { 281 | "type": "Feature", 282 | "id": "node/3403821230", 283 | "properties": { 284 | "@id": "node/3403821230", 285 | "amenity": "bar", 286 | "name": "Snack bar peter bar" 287 | }, 288 | "geometry": { 289 | "type": "Point", 290 | "coordinates": [ 291 | 12.4978786, 292 | 41.9001486 293 | ] 294 | } 295 | }, 296 | { 297 | "type": "Feature", 298 | "id": "node/3485963126", 299 | "properties": { 300 | "@id": "node/3485963126", 301 | "addr:housename": "Roma Termini", 302 | "addr:housenumber": "25", 303 | "addr:street": "Via Marsala", 304 | "amenity": "bar", 305 | "brand": "Chef Express", 306 | "building:level": "0", 307 | "name": "Buffet Roma Termini", 308 | "operator": "Chef Express S.p.A.", 309 | "ref:vatin": "IT00876120213" 310 | }, 311 | "geometry": { 312 | "type": "Point", 313 | "coordinates": [ 314 | 12.5023541, 315 | 41.9016306 316 | ] 317 | } 318 | }, 319 | { 320 | "type": "Feature", 321 | "id": "node/3551307620", 322 | "properties": { 323 | "@id": "node/3551307620", 324 | "addr:housenumber": "30-34", 325 | "addr:street": "Via Ludovisi", 326 | "amenity": "bar", 327 | "name": "Bar Ludovisi", 328 | "operator": "Doppio Zero Srl.", 329 | "phone": "+39 06 42016014", 330 | "ref:vatin": "IT11344331001" 331 | }, 332 | "geometry": { 333 | "type": "Point", 334 | "coordinates": [ 335 | 12.4887487, 336 | 41.9069283 337 | ] 338 | } 339 | }, 340 | { 341 | "type": "Feature", 342 | "id": "node/3578353919", 343 | "properties": { 344 | "@id": "node/3578353919", 345 | "addr:city": "Roma", 346 | "addr:country": "IT", 347 | "addr:housenumber": "36/37", 348 | "addr:postcode": "00185", 349 | "addr:street": "Via Merulana", 350 | "amenity": "bar", 351 | "name": "Il Pasticciaccio", 352 | "operator": "Angelo Blu s.r.l.", 353 | "phone": "+39 06 4826928", 354 | "ref:vatin": "IT09547101007" 355 | }, 356 | "geometry": { 357 | "type": "Point", 358 | "coordinates": [ 359 | 12.5005788, 360 | 41.8948307 361 | ] 362 | } 363 | }, 364 | { 365 | "type": "Feature", 366 | "id": "node/3607603463", 367 | "properties": { 368 | "@id": "node/3607603463", 369 | "addr:city": "Roma", 370 | "addr:housenumber": "22", 371 | "addr:postcode": "00185", 372 | "addr:street": "Via Tiburtina", 373 | "amenity": "bar", 374 | "name": "Buddha Bar" 375 | }, 376 | "geometry": { 377 | "type": "Point", 378 | "coordinates": [ 379 | 12.5121332, 380 | 41.8971197 381 | ] 382 | } 383 | } 384 | ] 385 | }; -------------------------------------------------------------------------------- /examples/data/colors.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | return [ 3 | {"loc":[41.575330,13.102411], "title":"aquamarine"}, 4 | {"loc":[41.575730,13.002411], "title":"black"}, 5 | {"loc":[41.807149,13.162994], "title":"blue"}, 6 | {"loc":[41.507149,13.172994], "title":"chocolate"}, 7 | {"loc":[41.847149,14.132994], "title":"coral"}, 8 | {"loc":[41.219190,13.062145], "title":"cyan"}, 9 | {"loc":[41.344190,13.242145], "title":"darkblue"}, 10 | {"loc":[41.679190,13.122145], "title":"Darkred"}, 11 | {"loc":[41.329190,13.192145], "title":"Darkgray"}, 12 | {"loc":[41.379290,13.122545], "title":"dodgerblue"}, 13 | {"loc":[41.409190,13.362145], "title":"gray"}, 14 | {"loc":[41.794008,12.583884], "title":"green"}, 15 | {"loc":[41.805008,12.982884], "title":"greenyellow"}, 16 | {"loc":[41.536175,13.273590], "title":"red"}, 17 | {"loc":[41.516175,13.373590], "title":"rosybrown"}, 18 | {"loc":[41.506175,13.273590], "title":"royalblue"}, 19 | {"loc":[41.836175,13.673590], "title":"salmon"}, 20 | {"loc":[41.796175,13.570590], "title":"seagreen"}, 21 | {"loc":[41.436175,13.573590], "title":"seashell"}, 22 | {"loc":[41.336175,13.973590], "title":"silver"}, 23 | {"loc":[41.236175,13.273590], "title":"skyblue"}, 24 | {"loc":[41.546175,13.473590], "title":"yellow"}, 25 | {"loc":[41.239190,13.032145], "title":"white"} 26 | ]; 27 | }); -------------------------------------------------------------------------------- /examples/data/csv2json.php: -------------------------------------------------------------------------------- 1 | $row[1], 'loc'=>array( (float)$row[4], (float)$row[5]) ); 28 | $n++; 29 | } 30 | } 31 | fclose($csvFile); 32 | } 33 | 34 | echo "generated $n records\n"; 35 | 36 | $json = json_encode($data); 37 | 38 | file_put_contents('cities15000.json', $json); 39 | 40 | ?> 41 | -------------------------------------------------------------------------------- /examples/data/custom-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/examples/data/custom-icon.png -------------------------------------------------------------------------------- /examples/data/htmlcolors.txt: -------------------------------------------------------------------------------- 1 | aliceblue:#f0f8ff 2 | antiquewhite:#faebd7 3 | aqua:#00ffff 4 | aquamarine:#7fffd4 5 | azure:#f0ffff 6 | beige:#f5f5dc 7 | bisque:#ffe4c4 8 | black:#000000 9 | blanchedalmond:#ffebcd 10 | blue:#0000ff 11 | blueviolet:#8a2be2 12 | brown:#a52a2a 13 | burlywood:#deb887 14 | cadetblue:#5f9ea0 15 | chartreuse:#7fff00 16 | chocolate:#d2691e 17 | coral:#ff7f50 18 | cornflowerblue:#6495ed 19 | cornsilk:#fff8dc 20 | crimson:#dc143c 21 | cyan:#00ffff 22 | darkblue:#00008b 23 | darkcyan:#008b8b 24 | darkgoldenrod:#b8860b 25 | darkgray:#a9a9a9 26 | darkgrey:#a9a9a9 27 | darkgreen:#006400 28 | darkkhaki:#bdb76b 29 | darkmagenta:#8b008b 30 | darkolivegreen:#556b2f 31 | darkorange:#ff8c00 32 | darkorchid:#9932cc 33 | darkred:#8b0000 34 | darksalmon:#e9967a 35 | darkseagreen:#8fbc8f 36 | darkslateblue:#483d8b 37 | darkslategray:#2f4f4f 38 | darkslategrey:#2f4f4f 39 | darkturquoise:#00ced1 40 | darkviolet:#9400d3 41 | deeppink:#ff1493 42 | deepskyblue:#00bfff 43 | dimgray:#696969 44 | dimgrey:#696969 45 | dodgerblue:#1e90ff 46 | firebrick:#b22222 47 | floralwhite:#fffaf0 48 | forestgreen:#228b22 49 | fuchsia:#ff00ff 50 | gainsboro:#dcdcdc 51 | ghostwhite:#f8f8ff 52 | gold:#ffd700 53 | goldenrod:#daa520 54 | gray:#808080 55 | grey:#808080 56 | green:#008000 57 | greenyellow:#adff2f 58 | honeydew:#f0fff0 59 | hotpink:#ff69b4 60 | indianred :#cd5c5c 61 | indigo :#4b0082 62 | ivory:#fffff0 63 | khaki:#f0e68c 64 | lavender:#e6e6fa 65 | lavenderblush:#fff0f5 66 | lawngreen:#7cfc00 67 | lemonchiffon:#fffacd 68 | lightblue:#add8e6 69 | lightcoral:#f08080 70 | lightcyan:#e0ffff 71 | lightgoldenrodyellow:#fafad2 72 | lightgray:#d3d3d3 73 | lightgrey:#d3d3d3 74 | lightgreen:#90ee90 75 | lightpink:#ffb6c1 76 | lightsalmon:#ffa07a 77 | lightseagreen:#20b2aa 78 | lightskyblue:#87cefa 79 | lightslategray:#778899 80 | lightslategrey:#778899 81 | lightsteelblue:#b0c4de 82 | lightyellow:#ffffe0 83 | lime:#00ff00 84 | limegreen:#32cd32 85 | linen:#faf0e6 86 | magenta:#ff00ff 87 | maroon:#800000 88 | mediumaquamarine:#66cdaa 89 | mediumblue:#0000cd 90 | mediumorchid:#ba55d3 91 | mediumpurple:#9370db 92 | mediumseagreen:#3cb371 93 | mediumslateblue:#7b68ee 94 | mediumspringgreen:#00fa9a 95 | mediumturquoise:#48d1cc 96 | mediumvioletred:#c71585 97 | midnightblue:#191970 98 | mintcream:#f5fffa 99 | mistyrose:#ffe4e1 100 | moccasin:#ffe4b5 101 | navajowhite:#ffdead 102 | navy:#000080 103 | oldlace:#fdf5e6 104 | olive:#808000 105 | olivedrab:#6b8e23 106 | orange:#ffa500 107 | orangered:#ff4500 108 | orchid:#da70d6 109 | palegoldenrod:#eee8aa 110 | palegreen:#98fb98 111 | paleturquoise:#afeeee 112 | palevioletred:#db7093 113 | papayawhip:#ffefd5 114 | peachpuff:#ffdab9 115 | peru:#cd853f 116 | pink:#ffc0cb 117 | plum:#dda0dd 118 | powderblue:#b0e0e6 119 | purple:#800080 120 | red:#ff0000 121 | rosybrown:#bc8f8f 122 | royalblue:#4169e1 123 | saddlebrown:#8b4513 124 | salmon:#fa8072 125 | sandybrown:#f4a460 126 | seagreen:#2e8b57 127 | seashell:#fff5ee 128 | sienna:#a0522d 129 | silver:#c0c0c0 130 | skyblue:#87ceeb 131 | slateblue:#6a5acd 132 | slategray:#708090 133 | slategrey:#708090 134 | snow:#fffafa 135 | springgreen:#00ff7f 136 | steelblue:#4682b4 137 | tan:#d2b48c 138 | teal:#008080 139 | thistle:#d8bfd8 140 | tomato:#ff6347 141 | turquoise:#40e0d0 142 | violet:#ee82ee 143 | wheat:#f5deb3 144 | white:#ffffff 145 | whitesmoke:#f5f5f5 146 | yellow:#ffff00 147 | yellowgreen:#9acd32 148 | -------------------------------------------------------------------------------- /examples/data/pharmacy.geojson.js: -------------------------------------------------------------------------------- 1 | var pharmacy = { 2 | "type": "FeatureCollection", 3 | "generator": "overpass-turbo", 4 | "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.", 5 | "timestamp": "2015-08-08T19:03:02Z", 6 | "features": [ 7 | { 8 | "type": "Feature", 9 | "id": "node/256572195", 10 | "properties": { 11 | "@id": "node/256572195", 12 | "addr:city": "Roma", 13 | "addr:country": "IT", 14 | "addr:housenumber": "35", 15 | "addr:postcode": "00185", 16 | "addr:street": "Piazza Manfredo Fanti", 17 | "amenity": "pharmacy", 18 | "dispensing": "yes", 19 | "name": "Farmacia Ricci" 20 | }, 21 | "geometry": { 22 | "type": "Point", 23 | "coordinates": [ 24 | 12.5020975, 25 | 41.8976328 26 | ] 27 | } 28 | }, 29 | { 30 | "type": "Feature", 31 | "id": "node/256592933", 32 | "properties": { 33 | "@id": "node/256592933", 34 | "amenity": "pharmacy", 35 | "created_by": "JOSM", 36 | "dispensing": "yes", 37 | "name": "P. Eugenio" 38 | }, 39 | "geometry": { 40 | "type": "Point", 41 | "coordinates": [ 42 | 12.50783, 43 | 41.8939352 44 | ] 45 | } 46 | }, 47 | { 48 | "type": "Feature", 49 | "id": "node/256592935", 50 | "properties": { 51 | "@id": "node/256592935", 52 | "amenity": "pharmacy", 53 | "created_by": "JOSM", 54 | "dispensing": "yes", 55 | "name": "Parafarmaciapiu'" 56 | }, 57 | "geometry": { 58 | "type": "Point", 59 | "coordinates": [ 60 | 12.5106824, 61 | 41.8930159 62 | ] 63 | } 64 | }, 65 | { 66 | "type": "Feature", 67 | "id": "node/256752359", 68 | "properties": { 69 | "@id": "node/256752359", 70 | "amenity": "pharmacy", 71 | "dispensing": "yes", 72 | "name": "De Sanctis" 73 | }, 74 | "geometry": { 75 | "type": "Point", 76 | "coordinates": [ 77 | 12.5057196, 78 | 41.8926807 79 | ] 80 | } 81 | }, 82 | { 83 | "type": "Feature", 84 | "id": "node/256752364", 85 | "properties": { 86 | "@id": "node/256752364", 87 | "amenity": "pharmacy", 88 | "dispensing": "yes", 89 | "name": "Trecca Mastrangeli" 90 | }, 91 | "geometry": { 92 | "type": "Point", 93 | "coordinates": [ 94 | 12.5063628, 95 | 41.8906915 96 | ] 97 | } 98 | }, 99 | { 100 | "type": "Feature", 101 | "id": "node/256752379", 102 | "properties": { 103 | "@id": "node/256752379", 104 | "amenity": "pharmacy", 105 | "created_by": "JOSM", 106 | "dispensing": "yes", 107 | "name": "Longo" 108 | }, 109 | "geometry": { 110 | "type": "Point", 111 | "coordinates": [ 112 | 12.505337, 113 | 41.8950203 114 | ] 115 | } 116 | }, 117 | { 118 | "type": "Feature", 119 | "id": "node/1166982158", 120 | "properties": { 121 | "@id": "node/1166982158", 122 | "amenity": "pharmacy", 123 | "name": "Farmacia Merulana Snc" 124 | }, 125 | "geometry": { 126 | "type": "Point", 127 | "coordinates": [ 128 | 12.5022586, 129 | 41.8910825 130 | ] 131 | } 132 | }, 133 | { 134 | "type": "Feature", 135 | "id": "node/1166983851", 136 | "properties": { 137 | "@id": "node/1166983851", 138 | "amenity": "pharmacy", 139 | "name": "Farmacia Cuzzocrea Dottor Antonino" 140 | }, 141 | "geometry": { 142 | "type": "Point", 143 | "coordinates": [ 144 | 12.5033866, 145 | 41.8941826 146 | ] 147 | } 148 | }, 149 | { 150 | "type": "Feature", 151 | "id": "node/1166984396", 152 | "properties": { 153 | "@id": "node/1166984396", 154 | "addr:city": "Roma", 155 | "addr:country": "IT", 156 | "addr:housenumber": "35", 157 | "addr:street": "Via dello Statuto", 158 | "amenity": "pharmacy", 159 | "dispensing": "yes", 160 | "name": "Farmacia Allo Statuto", 161 | "opening_hours": "24/7", 162 | "operator": "Dr. Pierluciano Pucci" 163 | }, 164 | "geometry": { 165 | "type": "Point", 166 | "coordinates": [ 167 | 12.5010875, 168 | 41.8950388 169 | ] 170 | } 171 | }, 172 | { 173 | "type": "Feature", 174 | "id": "node/1324437899", 175 | "properties": { 176 | "@id": "node/1324437899", 177 | "addr:city": "Roma", 178 | "addr:country": "IT", 179 | "addr:housenumber": "49", 180 | "addr:street": "Piazza Barberini", 181 | "amenity": "pharmacy", 182 | "fax": "+39 06 48912560", 183 | "name": "Farmacia Internazionale", 184 | "opening_hours": "24/7", 185 | "operator": "Dottoressa Cervini Teresa", 186 | "phone": "+39 06 4825456" 187 | }, 188 | "geometry": { 189 | "type": "Point", 190 | "coordinates": [ 191 | 12.4884424, 192 | 41.9033713 193 | ] 194 | } 195 | }, 196 | { 197 | "type": "Feature", 198 | "id": "node/1662148782", 199 | "properties": { 200 | "@id": "node/1662148782", 201 | "amenity": "pharmacy", 202 | "name": "Farmacia", 203 | "wheelchair": "limited" 204 | }, 205 | "geometry": { 206 | "type": "Point", 207 | "coordinates": [ 208 | 12.493189, 209 | 41.9008595 210 | ] 211 | } 212 | }, 213 | { 214 | "type": "Feature", 215 | "id": "node/1979465981", 216 | "properties": { 217 | "@id": "node/1979465981", 218 | "addr:city": "Roma", 219 | "addr:country": "IT", 220 | "addr:housenumber": "29", 221 | "addr:postcode": "00185", 222 | "addr:street": "Via Marsala", 223 | "amenity": "pharmacy", 224 | "dispensing": "yes", 225 | "name": "Farmacrimi", 226 | "opening_hours": "Mo-Su,PH 07:30-22:00", 227 | "operator": "V. Crimi e Co.", 228 | "phone": "+39 06 4745421", 229 | "ref:vatin": "IT10491561006", 230 | "website": "http://www.farmacrimi.it" 231 | }, 232 | "geometry": { 233 | "type": "Point", 234 | "coordinates": [ 235 | 12.5031277, 236 | 41.9014832 237 | ] 238 | } 239 | }, 240 | { 241 | "type": "Feature", 242 | "id": "node/2116655793", 243 | "properties": { 244 | "@id": "node/2116655793", 245 | "amenity": "pharmacy", 246 | "dispensing": "yes", 247 | "name": "Farmacia Viminale" 248 | }, 249 | "geometry": { 250 | "type": "Point", 251 | "coordinates": [ 252 | 12.494817, 253 | 41.8995944 254 | ] 255 | } 256 | }, 257 | { 258 | "type": "Feature", 259 | "id": "node/3061568281", 260 | "properties": { 261 | "@id": "node/3061568281", 262 | "addr:city": "Roma", 263 | "addr:housenumber": "89", 264 | "addr:postcode": "00187", 265 | "addr:street": "Via della Stamperia", 266 | "amenity": "pharmacy", 267 | "name": "Farmacia Pesci" 268 | }, 269 | "geometry": { 270 | "type": "Point", 271 | "coordinates": [ 272 | 12.4836798, 273 | 41.9009134 274 | ] 275 | } 276 | } 277 | ] 278 | }; -------------------------------------------------------------------------------- /examples/data/restaurant.geojson.js: -------------------------------------------------------------------------------- 1 | var restaurant = { 2 | "type": "FeatureCollection", 3 | "generator": "overpass-turbo", 4 | "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.", 5 | "timestamp": "2015-08-08T19:02:02Z", 6 | "features": [ 7 | { 8 | "type": "Feature", 9 | "id": "node/252601050", 10 | "properties": { 11 | "@id": "node/252601050", 12 | "amenity": "restaurant", 13 | "cuisine": "chinese", 14 | "name": "Hang Zhou" 15 | }, 16 | "geometry": { 17 | "type": "Point", 18 | "coordinates": [ 19 | 12.4994694, 20 | 41.8957882 21 | ] 22 | } 23 | }, 24 | { 25 | "type": "Feature", 26 | "id": "node/1479450328", 27 | "properties": { 28 | "@id": "node/1479450328", 29 | "amenity": "restaurant", 30 | "cuisine": "italian", 31 | "name": "Est Est Est !", 32 | "website": "www.anticapizzeriaricciroma.com" 33 | }, 34 | "geometry": { 35 | "type": "Point", 36 | "coordinates": [ 37 | 12.4926565, 38 | 41.8989567 39 | ] 40 | } 41 | }, 42 | { 43 | "type": "Feature", 44 | "id": "node/1530298843", 45 | "properties": { 46 | "@id": "node/1530298843", 47 | "amenity": "restaurant", 48 | "name": "Hostaria Isidoro Al Colosseo" 49 | }, 50 | "geometry": { 51 | "type": "Point", 52 | "coordinates": [ 53 | 12.4986415, 54 | 41.8888149 55 | ] 56 | } 57 | }, 58 | { 59 | "type": "Feature", 60 | "id": "node/1536870110", 61 | "properties": { 62 | "@id": "node/1536870110", 63 | "addr:housenumber": "40", 64 | "addr:street": "Largo Corrado Ricci", 65 | "amenity": "restaurant", 66 | "name": "Angelino ai Fori", 67 | "wheelchair": "limited" 68 | }, 69 | "geometry": { 70 | "type": "Point", 71 | "coordinates": [ 72 | 12.488007, 73 | 41.8930475 74 | ] 75 | } 76 | }, 77 | { 78 | "type": "Feature", 79 | "id": "node/1536870121", 80 | "properties": { 81 | "@id": "node/1536870121", 82 | "addr:housenumber": "37", 83 | "amenity": "restaurant", 84 | "cuisine": "pizza", 85 | "name": "Pizzeria Imperiale", 86 | "wheelchair": "limited" 87 | }, 88 | "geometry": { 89 | "type": "Point", 90 | "coordinates": [ 91 | 12.4882146, 92 | 41.8931487 93 | ] 94 | } 95 | }, 96 | { 97 | "type": "Feature", 98 | "id": "node/1542760517", 99 | "properties": { 100 | "@id": "node/1542760517", 101 | "amenity": "restaurant", 102 | "cuisine": "italian", 103 | "name": "Made in Sud" 104 | }, 105 | "geometry": { 106 | "type": "Point", 107 | "coordinates": [ 108 | 12.4956215, 109 | 41.8895889 110 | ] 111 | } 112 | }, 113 | { 114 | "type": "Feature", 115 | "id": "node/1589901714", 116 | "properties": { 117 | "@id": "node/1589901714", 118 | "amenity": "restaurant", 119 | "name": "Kabir Fast Food" 120 | }, 121 | "geometry": { 122 | "type": "Point", 123 | "coordinates": [ 124 | 12.5046274, 125 | 41.8956069 126 | ] 127 | } 128 | }, 129 | { 130 | "type": "Feature", 131 | "id": "node/1609106628", 132 | "properties": { 133 | "@id": "node/1609106628", 134 | "amenity": "restaurant", 135 | "cuisine": "italian", 136 | "name": "Pretoriana" 137 | }, 138 | "geometry": { 139 | "type": "Point", 140 | "coordinates": [ 141 | 12.5043739, 142 | 41.9050278 143 | ] 144 | } 145 | }, 146 | { 147 | "type": "Feature", 148 | "id": "node/1660404815", 149 | "properties": { 150 | "@id": "node/1660404815", 151 | "amenity": "restaurant", 152 | "cuisine": "regional", 153 | "name": "Al Vino al Vino", 154 | "restaurant:type": "enoteca" 155 | }, 156 | "geometry": { 157 | "type": "Point", 158 | "coordinates": [ 159 | 12.4903691, 160 | 41.8955158 161 | ] 162 | } 163 | }, 164 | { 165 | "type": "Feature", 166 | "id": "node/1660404843", 167 | "properties": { 168 | "@id": "node/1660404843", 169 | "amenity": "restaurant", 170 | "cuisine": "regional", 171 | "name": "Taverna Romana" 172 | }, 173 | "geometry": { 174 | "type": "Point", 175 | "coordinates": [ 176 | 12.4894514, 177 | 41.8940973 178 | ] 179 | } 180 | }, 181 | { 182 | "type": "Feature", 183 | "id": "node/1660412593", 184 | "properties": { 185 | "@id": "node/1660412593", 186 | "addr:housenumber": "313", 187 | "amenity": "restaurant", 188 | "cuisine": "regional", 189 | "email": "cavour313@libero.it", 190 | "fax": "+39 06 6785496", 191 | "name": "Enoteca Cavour 313", 192 | "operator": "Enoteca Cavour 313 s.r.l.", 193 | "phone": "+39 06 6785496", 194 | "ref:vatin": "IT01238481004", 195 | "restaurant:type": "enoteca" 196 | }, 197 | "geometry": { 198 | "type": "Point", 199 | "coordinates": [ 200 | 12.489126, 201 | 41.8937195 202 | ] 203 | } 204 | }, 205 | { 206 | "type": "Feature", 207 | "id": "node/1688847325", 208 | "properties": { 209 | "@id": "node/1688847325", 210 | "amenity": "restaurant", 211 | "cuisine": "italian", 212 | "name": "Gran Caffe Martini & Rossi" 213 | }, 214 | "geometry": { 215 | "type": "Point", 216 | "coordinates": [ 217 | 12.4941649, 218 | 41.8897129 219 | ] 220 | } 221 | }, 222 | { 223 | "type": "Feature", 224 | "id": "node/1688847351", 225 | "properties": { 226 | "@id": "node/1688847351", 227 | "addr:housenumber": "5", 228 | "amenity": "restaurant", 229 | "cuisine": "italian", 230 | "name": "Al Gladiatore", 231 | "phone": "+39 06 77209496" 232 | }, 233 | "geometry": { 234 | "type": "Point", 235 | "coordinates": [ 236 | 12.494046, 237 | 41.8894459 238 | ] 239 | } 240 | }, 241 | { 242 | "type": "Feature", 243 | "id": "node/1688847355", 244 | "properties": { 245 | "@id": "node/1688847355", 246 | "amenity": "restaurant", 247 | "cuisine": "italian", 248 | "name": "Royal art café" 249 | }, 250 | "geometry": { 251 | "type": "Point", 252 | "coordinates": [ 253 | 12.4942704, 254 | 41.8899551 255 | ] 256 | } 257 | }, 258 | { 259 | "type": "Feature", 260 | "id": "node/1731734008", 261 | "properties": { 262 | "@id": "node/1731734008", 263 | "addr:city": "Roma", 264 | "addr:country": "IT", 265 | "addr:housenumber": "66", 266 | "addr:postcode": "00185", 267 | "addr:street": "Via Montebello", 268 | "amenity": "restaurant", 269 | "cuisine": "asian;pinoy;chinese", 270 | "name": "Asian Delight", 271 | "opening_hours": "Mo-Su 10:00-22:00; Sa 12:00-21:00", 272 | "operator": "F. Cojeda Contado SNC", 273 | "phone": "+39 06 48913216" 274 | }, 275 | "geometry": { 276 | "type": "Point", 277 | "coordinates": [ 278 | 12.5002433, 279 | 41.9055106 280 | ] 281 | } 282 | }, 283 | { 284 | "type": "Feature", 285 | "id": "node/1923912448", 286 | "properties": { 287 | "@id": "node/1923912448", 288 | "addr:housenumber": "195", 289 | "amenity": "restaurant", 290 | "contact:phone": "+39 06 4824888", 291 | "drive_in": "no", 292 | "name": "L'archetto di Cavour", 293 | "wheelchair": "limited" 294 | }, 295 | "geometry": { 296 | "type": "Point", 297 | "coordinates": [ 298 | 12.4935056, 299 | 41.8951179 300 | ] 301 | } 302 | }, 303 | { 304 | "type": "Feature", 305 | "id": "node/1926196447", 306 | "properties": { 307 | "@id": "node/1926196447", 308 | "amenity": "restaurant", 309 | "cuisine": "italian", 310 | "name": "Fantasy" 311 | }, 312 | "geometry": { 313 | "type": "Point", 314 | "coordinates": [ 315 | 12.49949, 316 | 41.9045268 317 | ] 318 | } 319 | }, 320 | { 321 | "type": "Feature", 322 | "id": "node/1952358265", 323 | "properties": { 324 | "@id": "node/1952358265", 325 | "addr:city": "Roma", 326 | "addr:country": "IT", 327 | "addr:housenumber": "44/46", 328 | "addr:postcode": "00185", 329 | "addr:street": "Via Tiburtina", 330 | "amenity": "restaurant", 331 | "cuisine": "pizza", 332 | "name": "Pizzeria L'Economica", 333 | "operator": "Lori Giulio & C. s.a.s.", 334 | "oven": "wood_fired", 335 | "ref:vatin": "IT0390762210001", 336 | "restaurant:type:it": "pizzeria" 337 | }, 338 | "geometry": { 339 | "type": "Point", 340 | "coordinates": [ 341 | 12.5127833, 342 | 41.8974467 343 | ] 344 | } 345 | }, 346 | { 347 | "type": "Feature", 348 | "id": "node/2612010515", 349 | "properties": { 350 | "@id": "node/2612010515", 351 | "addr:city": "Roma", 352 | "addr:housenumber": "21", 353 | "addr:postcode": "00187", 354 | "addr:street": "Via della Cordonata", 355 | "amenity": "restaurant", 356 | "cuisine": "regional", 357 | "name": "Santa Cristina al Quirinale", 358 | "phone": "+39 06 69925485", 359 | "website": "http://www.ristorantesantacristinaalquirinale.it" 360 | }, 361 | "geometry": { 362 | "type": "Point", 363 | "coordinates": [ 364 | 12.4864783, 365 | 41.8970834 366 | ] 367 | } 368 | }, 369 | { 370 | "type": "Feature", 371 | "id": "node/2735002326", 372 | "properties": { 373 | "@id": "node/2735002326", 374 | "addr:housenumber": "315", 375 | "amenity": "restaurant", 376 | "name": "Baires Imperiale", 377 | "phone": "+39 06 69202164" 378 | }, 379 | "geometry": { 380 | "type": "Point", 381 | "coordinates": [ 382 | 12.4888985, 383 | 41.8936579 384 | ] 385 | } 386 | }, 387 | { 388 | "type": "Feature", 389 | "id": "node/2735002356", 390 | "properties": { 391 | "@id": "node/2735002356", 392 | "addr:housenumber": "27", 393 | "amenity": "restaurant", 394 | "cuisine": "pizza", 395 | "name": "Tomoko Tudini", 396 | "phone": "+39 06 4817586; +39 06 4818487" 397 | }, 398 | "geometry": { 399 | "type": "Point", 400 | "coordinates": [ 401 | 12.4992448, 402 | 41.8994035 403 | ] 404 | } 405 | }, 406 | { 407 | "type": "Feature", 408 | "id": "node/2925136606", 409 | "properties": { 410 | "@id": "node/2925136606", 411 | "addr:city": "Roma", 412 | "addr:housenumber": "56", 413 | "addr:postcode": "00187", 414 | "addr:street": "Vicolo dei Modelli", 415 | "amenity": "restaurant", 416 | "name": "La Fontana di Venere" 417 | }, 418 | "geometry": { 419 | "type": "Point", 420 | "coordinates": [ 421 | 12.4844365, 422 | 41.900612 423 | ] 424 | } 425 | }, 426 | { 427 | "type": "Feature", 428 | "id": "node/2939415080", 429 | "properties": { 430 | "@id": "node/2939415080", 431 | "addr:city": "Roma", 432 | "addr:country": "IT", 433 | "addr:housename": "La Taverna Italiana", 434 | "addr:housenumber": "385", 435 | "addr:postcode": "00185", 436 | "addr:street": "Via Giovanni Giolitti", 437 | "amenity": "restaurant", 438 | "name": "La Taverna Italiana" 439 | }, 440 | "geometry": { 441 | "type": "Point", 442 | "coordinates": [ 443 | 12.5109193, 444 | 41.8940946 445 | ] 446 | } 447 | }, 448 | { 449 | "type": "Feature", 450 | "id": "node/3052495271", 451 | "properties": { 452 | "@id": "node/3052495271", 453 | "addr:city": "Roma", 454 | "addr:housenumber": "24-24a", 455 | "addr:street": "Via dei Santi Quattro", 456 | "amenity": "restaurant", 457 | "cuisine": "pizza", 458 | "name": "Li Rioni" 459 | }, 460 | "geometry": { 461 | "type": "Point", 462 | "coordinates": [ 463 | 12.4978334, 464 | 41.8886879 465 | ] 466 | } 467 | }, 468 | { 469 | "type": "Feature", 470 | "id": "node/3061568272", 471 | "properties": { 472 | "@id": "node/3061568272", 473 | "addr:city": "Roma", 474 | "addr:housenumber": "95", 475 | "addr:postcode": "00186", 476 | "addr:street": "Via in Arcione", 477 | "amenity": "restaurant", 478 | "name": "Al Presidente" 479 | }, 480 | "geometry": { 481 | "type": "Point", 482 | "coordinates": [ 483 | 12.4852166, 484 | 41.9015405 485 | ] 486 | } 487 | }, 488 | { 489 | "type": "Feature", 490 | "id": "node/3061568276", 491 | "properties": { 492 | "@id": "node/3061568276", 493 | "addr:city": "Roma", 494 | "addr:housenumber": "41", 495 | "addr:postcode": "00186", 496 | "addr:street": "Via del Lavatore", 497 | "amenity": "restaurant", 498 | "name": "Birreria" 499 | }, 500 | "geometry": { 501 | "type": "Point", 502 | "coordinates": [ 503 | 12.4840258, 504 | 41.9008905 505 | ] 506 | } 507 | }, 508 | { 509 | "type": "Feature", 510 | "id": "node/3061568277", 511 | "properties": { 512 | "@id": "node/3061568277", 513 | "addr:city": "Roma", 514 | "addr:housenumber": "34", 515 | "addr:street": "Via del Lavatore", 516 | "amenity": "restaurant", 517 | "name": "Da Cecere" 518 | }, 519 | "geometry": { 520 | "type": "Point", 521 | "coordinates": [ 522 | 12.4844496, 523 | 41.9011041 524 | ] 525 | } 526 | }, 527 | { 528 | "type": "Feature", 529 | "id": "node/3101353149", 530 | "properties": { 531 | "@id": "node/3101353149", 532 | "addr:city": "Roma", 533 | "addr:housenumber": "9", 534 | "addr:street": "Via della Madonna dei Monti", 535 | "amenity": "restaurant", 536 | "cuisine": "italian", 537 | "name": "La Taverna dei Fori Imperiali" 538 | }, 539 | "geometry": { 540 | "type": "Point", 541 | "coordinates": [ 542 | 12.4883814, 543 | 41.8939729 544 | ] 545 | } 546 | }, 547 | { 548 | "type": "Feature", 549 | "id": "node/3152274226", 550 | "properties": { 551 | "@id": "node/3152274226", 552 | "addr:city": "Roma", 553 | "addr:housenumber": "7", 554 | "addr:postcode": "00185", 555 | "addr:street": "Via Principe Amedeo", 556 | "amenity": "restaurant", 557 | "cuisine": "italian", 558 | "name": "Trattoria il Gallo Nero" 559 | }, 560 | "geometry": { 561 | "type": "Point", 562 | "coordinates": [ 563 | 12.4980494, 564 | 41.9000787 565 | ] 566 | } 567 | }, 568 | { 569 | "type": "Feature", 570 | "id": "node/3190236550", 571 | "properties": { 572 | "@id": "node/3190236550", 573 | "addr:city": "Roma", 574 | "addr:housenumber": "91", 575 | "addr:street": "Via del Lavatore", 576 | "amenity": "restaurant", 577 | "cuisine": "pizza", 578 | "name": "Piccolo Buco", 579 | "phone": "+39 06 69380163" 580 | }, 581 | "geometry": { 582 | "type": "Point", 583 | "coordinates": [ 584 | 12.4846406, 585 | 41.9013199 586 | ] 587 | } 588 | }, 589 | { 590 | "type": "Feature", 591 | "id": "node/3379569487", 592 | "properties": { 593 | "@id": "node/3379569487", 594 | "addr:city": "Roma", 595 | "addr:country": "IT", 596 | "addr:housenumber": "54", 597 | "addr:postcode": "00185", 598 | "addr:street": "Via Varese", 599 | "amenity": "restaurant", 600 | "cuisine": "italian", 601 | "email": "info@meidinnapols.it", 602 | "name": "Meid in Nepols", 603 | "oven": "wood_fired", 604 | "phone": "+39 06 44704131", 605 | "website": "http://www.meidinnepols.com/" 606 | }, 607 | "geometry": { 608 | "type": "Point", 609 | "coordinates": [ 610 | 12.5061437, 611 | 41.9023658 612 | ] 613 | } 614 | }, 615 | { 616 | "type": "Feature", 617 | "id": "node/3548494475", 618 | "properties": { 619 | "@id": "node/3548494475", 620 | "addr:street": "San Martino dei Monte", 621 | "amenity": "restaurant", 622 | "cuisine": "italian", 623 | "name": "La Forchetta di Oro", 624 | "opening_hours": "mezze, serra", 625 | "smoking": "no", 626 | "wheelchair": "yes" 627 | }, 628 | "geometry": { 629 | "type": "Point", 630 | "coordinates": [ 631 | 12.4988453, 632 | 41.8956182 633 | ] 634 | } 635 | }, 636 | { 637 | "type": "Feature", 638 | "id": "node/3554601726", 639 | "properties": { 640 | "@id": "node/3554601726", 641 | "addr:city": "Roma", 642 | "addr:country": "IT", 643 | "addr:housenumber": "107", 644 | "addr:postcode": "00184", 645 | "addr:street": "Via Cavour", 646 | "amenity": "restaurant", 647 | "cuisine": "italian", 648 | "name": "Ristorante-Pizzeria Gallo Matto", 649 | "outdoor_seating": "yes", 650 | "phone": "+39 06 470354" 651 | }, 652 | "geometry": { 653 | "type": "Point", 654 | "coordinates": [ 655 | 12.4954961, 656 | 41.8970481 657 | ] 658 | } 659 | }, 660 | { 661 | "type": "Feature", 662 | "id": "node/3554607395", 663 | "properties": { 664 | "@id": "node/3554607395", 665 | "addr:city": "Roma", 666 | "addr:country": "IT", 667 | "addr:housenumber": "86", 668 | "addr:postcode": "00184", 669 | "addr:street": "Via Leonina", 670 | "amenity": "restaurant", 671 | "cuisine": "italian", 672 | "name": "Ristorante Ragno d'Oro", 673 | "outdoor_seating": "yes", 674 | "phone": "+39 06 4882003" 675 | }, 676 | "geometry": { 677 | "type": "Point", 678 | "coordinates": [ 679 | 12.492441, 680 | 41.8949731 681 | ] 682 | } 683 | }, 684 | { 685 | "type": "Feature", 686 | "id": "node/3575003304", 687 | "properties": { 688 | "@id": "node/3575003304", 689 | "addr:city": "Roma", 690 | "addr:housenumber": "10", 691 | "addr:postcode": "00185", 692 | "addr:street": "Via del Castro Pretorio", 693 | "amenity": "restaurant", 694 | "cuisine": "japanese", 695 | "name": "Hokkaido", 696 | "opening_hours": "Mo-Su 11:00-15:00,18:00-24:00", 697 | "phone": "+39 06 45505297" 698 | }, 699 | "geometry": { 700 | "type": "Point", 701 | "coordinates": [ 702 | 12.5046543, 703 | 41.9012469 704 | ] 705 | } 706 | }, 707 | { 708 | "type": "Feature", 709 | "id": "node/3658239261", 710 | "properties": { 711 | "@id": "node/3658239261", 712 | "addr:city": "Roma", 713 | "addr:housenumber": "58", 714 | "addr:street": "Piazza dei Santi Apostoli", 715 | "amenity": "restaurant", 716 | "cuisine": "italian", 717 | "name": "Bibo" 718 | }, 719 | "geometry": { 720 | "type": "Point", 721 | "coordinates": [ 722 | 12.4833635, 723 | 41.8974063 724 | ] 725 | } 726 | } 727 | ] 728 | }; -------------------------------------------------------------------------------- /examples/fuzzy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

Custom Filter Example: search markers with Fuzzy Search, using Fuse.js by Kirollos Risk

15 |
16 | 17 |
18 | Search restaurants in Rome, data by OSM Overpass
19 | Example: pizza, vege, japa, giappo, cucina romana, chine ... 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/geocoder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 |

Leaflet.Control.Search

22 | 23 |

Google GeoCoding API: search locations name by Google Maps API

24 |
25 | 26 | 27 | 28 | 29 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/geocoding-google.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 |

Leaflet.Control.Search

22 | 23 |

Google GeoCoding API: search locations name by Google Maps API

24 |
25 | 26 |
27 | Search values:
28 | Google Geocoding API
29 | developers.google.com 30 |
31 | 32 | 33 | 34 | 35 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/geocoding-nominatim.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | 22 |

Leaflet.Control.Search

23 | 24 |

GeoCode Search Example: search locations name by Nominatim Openstreetmap Service

25 |
26 | 27 |
28 | Search values:
29 | OpenStreetMap Data offer by
30 | nominatim.osm.org 31 |
32 | 33 | 34 | 35 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/geojson-layer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

GeoJSON Example: search vector features in GeoJSON layer by property

15 |
16 | 17 |
18 | Search US states name: 19 | Alabama, Arizona, Colorado, Maryland, Michigan, North Carolina, Pennsylvania, Wyoming ... 20 |
21 | 22 | 23 | 24 | 25 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /examples/jsonp-filtered.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 |

Leaflet.Control.Search

27 | 28 |

JSONP Example: search locations by third party jsonp service, with filter data

29 |
30 | 31 |
32 | Search values:
33 | OpenStreetMap Data offer by MapQuest Open Platform
34 | open.mapquestapi.com 35 |
36 | 37 |
native JSON
38 | 39 | 40 | 41 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/jsonp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

JSONP Example: search locations by jsonp service

15 |
16 | 17 |
18 | Search values:
19 | aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ... 20 |
21 | 22 | 23 | 24 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/location-url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 |

Leaflet.Control.Search

22 | 23 |

Location on remote page: search locations name and open it in osm website

24 |
25 | 26 |
27 | Search values:
28 | OpenStreetMap Data offer by
29 | nominatim.osm.org 30 |
31 | 32 | 33 | 34 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/methods.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Leaflet.Control.Search

14 | 15 |

Methods Example: search text by method

16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 | Search values:
24 | aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ... 25 |
26 | 27 | 28 | 29 | 30 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /examples/mobile.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | margin:0; 3 | padding:0; 4 | } 5 | 6 | h3 { 7 | font-size:.85em; 8 | } 9 | h3,h4 { 10 | white-space:nowrap; 11 | float:left; 12 | margin:2px; 13 | padding:0; 14 | } 15 | #copy { 16 | right:-4px; 17 | } 18 | #map { 19 | position:absolute; 20 | top:22px; 21 | left:0; 22 | right:0; 23 | bottom:0; 24 | border:none; 25 | border-top:1px solid #1978cf; 26 | height:auto; 27 | width:auto; 28 | } 29 | #post-it { 30 | position:absolute; 31 | right:5px; 32 | top:40px; 33 | height:90px; 34 | width:90px; 35 | font-size:.65em; 36 | } 37 | 38 | /********** Leaflet Customizations for **********/ 39 | 40 | /* CONTROLS */ 41 | .leaflet-bottom .leaflet-control, 42 | .leaflet-left .leaflet-control { 43 | box-shadow: 0 0 8px rgba(0,0,0,0.4); 44 | border: 2px solid #1978cf; 45 | border-radius:6px; 46 | } 47 | .leaflet-left.leaflet-top .leaflet-control, 48 | .leaflet-touch .leaflet-left .leaflet-control { 49 | margin-left: 8px; 50 | margin-top: 8px; 51 | margin-bottom: 0; 52 | } 53 | .leaflet-left.leaflet-bottom .leaflet-control { 54 | margin-left: 8px; 55 | margin-bottom: 8px; 56 | margin-top: 0; 57 | } 58 | 59 | /* ZOOM */ 60 | .leaflet-control-zoom, 61 | .leaflet-touch .leaflet-control-zoom { 62 | border-radius:4px; 63 | } 64 | .leaflet-control-zoom a, 65 | .leaflet-touch .leaflet-control-zoom a, 66 | .leaflet-bar a, 67 | .leaflet-touch .leaflet-bar a { 68 | background-color: rgba(255, 255, 255, 0.8); 69 | width: 32px; 70 | height: 28px; 71 | } 72 | .leaflet-touch .leaflet-control-zoom-out { 73 | line-height: 24px; 74 | } 75 | .leaflet-control-zoom a:hover, 76 | .leaflet-touch .leaflet-control-zoom a:hover, 77 | .leaflet-bar a:hover, 78 | .leaflet-touch .leaflet-bar a:hover { 79 | background-color: #fff; 80 | width: 32px; 81 | height: 28px; 82 | } 83 | 84 | .leaflet-touch .leaflet-bar a:first-child { 85 | -webkit-border-top-left-radius: 4px; 86 | border-top-left-radius: 4px; 87 | -webkit-border-top-right-radius: 4px; 88 | border-top-right-radius: 4px; 89 | } 90 | .leaflet-touch .leaflet-bar a:last-child { 91 | -webkit-border-bottom-left-radius: 4px; 92 | border-bottom-left-radius: 4px; 93 | -webkit-border-bottom-right-radius: 4px; 94 | border-bottom-right-radius: 4px; 95 | border-bottom: none; 96 | } 97 | 98 | /* ATTRIBUTION*/ 99 | .leaflet-control.leaflet-control-attribution { 100 | border:none; 101 | padding:0 4px 2px 4px; 102 | margin:0 -4px -4px 0; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /examples/mobile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet Search Mobile Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Leaflet.Control.Search   Mobile example

16 | 17 |
18 | 19 |
20 | Search values:
21 | OpenStreetMap Data offer by MapQuest Open Platform
22 | open.mapquestapi.com 23 |
24 | 25 | 26 | 27 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/multiple-layers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |

Leaflet.Control.Search

50 | 51 |

Multiple Layers Example: search markers in multiple layers

52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /examples/multiple-results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

Multiple search Example: search multiple markers have same title

15 |
16 | 17 |
18 | Search values:
19 | Darkgray, dodgerblue, gray, green, seashell 20 |
21 | 22 | 23 | 24 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /examples/nominatim.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/outside.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 29 | 30 | 31 | 32 |

Leaflet.Control.Search

33 | 34 |

Outside Div Example: move Seach Control outside the map div

35 |
36 |
37 | 38 |
39 | Search values:
40 | aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ... 41 |
42 | 43 | 44 | 45 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /examples/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

Multiple Layers Example: search markers in multiple layers

15 |
16 | 17 | 18 | 19 | 20 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/requirejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

Leaflet.Control.Search

15 | 16 |

RequireJs Example: load library by AMD module compatible

17 |
18 | 19 |
20 | Search values:
21 | aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ... 22 |
23 | 24 | 25 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/search.php: -------------------------------------------------------------------------------- 1 | 0, 'errmsg'=>'specify q parameter') ) ); 53 | 54 | $data = json_decode('[ 55 | {"loc":[41.575330,13.102411], "title":"aquamarine"}, 56 | {"loc":[41.575730,13.002411], "title":"black"}, 57 | {"loc":[41.807149,13.162994], "title":"blue"}, 58 | {"loc":[41.507149,13.172994], "title":"chocolate"}, 59 | {"loc":[41.847149,14.132994], "title":"coral"}, 60 | {"loc":[41.219190,13.062145], "title":"cyan"}, 61 | {"loc":[41.344190,13.242145], "title":"darkblue"}, 62 | {"loc":[41.679190,13.122145], "title":"darkred"}, 63 | {"loc":[41.329190,13.192145], "title":"darkgray"}, 64 | {"loc":[41.379290,13.122545], "title":"dodgerblue"}, 65 | {"loc":[41.409190,13.362145], "title":"gray"}, 66 | {"loc":[41.794008,12.583884], "title":"green"}, 67 | {"loc":[41.805008,12.982884], "title":"greenyellow"}, 68 | {"loc":[41.536175,13.273590], "title":"red"}, 69 | {"loc":[41.516175,13.373590], "title":"rosybrown"}, 70 | {"loc":[41.506175,13.173590], "title":"royalblue"}, 71 | {"loc":[41.836175,13.673590], "title":"salmon"}, 72 | {"loc":[41.796175,13.570590], "title":"seagreen"}, 73 | {"loc":[41.436175,13.573590], "title":"seashell"}, 74 | {"loc":[41.336175,13.973590], "title":"silver"}, 75 | {"loc":[41.236175,13.273590], "title":"skyblue"}, 76 | {"loc":[41.546175,13.473590], "title":"yellow"}, 77 | {"loc":[41.239190,13.032145], "title":"white"} 78 | ]',true); //SIMULATE A DATABASE data 79 | //the searched field is: title 80 | 81 | if(isset($_GET['cities'])) //SIMULATE A BIG DATABASE, for ajax-bulk.html example 82 | $data = json_decode( file_get_contents('cities15000.json'), true); 83 | //load big data store, cities15000.json (about 14000 records) 84 | 85 | function searchInit($text) //search initial text in titles 86 | { 87 | $reg = "/^".$_GET['q']."/i"; //initial case insensitive searching 88 | return (bool)@preg_match($reg, $text['title']); 89 | } 90 | $fdata = array_filter($data, 'searchInit'); //filter data 91 | $fdata = array_values($fdata); //reset $fdata indexs 92 | 93 | $JSON = json_encode($fdata,true); 94 | 95 | #if($_SERVER['REMOTE_ADDR']=='127.0.0.1') sleep(1); 96 | //simulate connection latency for localhost tests 97 | @header("Content-type: application/json; charset=utf-8"); 98 | 99 | if(isset($_GET['callback']) and !empty($_GET['callback'])) //support for JSONP request 100 | echo $_GET['callback']."($JSON)"; 101 | else 102 | echo $JSON; //AJAX request 103 | 104 | 105 | ?> 106 | -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet.Control.Search

13 | 14 |

Simple Example: search markers in layer by title

15 |
16 | 17 |
18 | Search values:
19 | aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ... 20 |
21 | 22 | 23 | 24 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background:#b5d0d0; 3 | color:#285585; 4 | font-family:Arial; 5 | } 6 | body#home { 7 | background:url('../images/back.png') no-repeat top left #b5d0d0; 8 | margin-left:150px; 9 | } 10 | 11 | a { 12 | color:#1978cf; 13 | } 14 | a:hover { 15 | color:#fff; 16 | } 17 | h2, h3, h4 { 18 | white-space:nowrap; 19 | margin:1em 0 0 0; 20 | } 21 | h3 a, 22 | h3 a:hover { 23 | text-decoration:none; 24 | } 25 | #desc { 26 | float: left; 27 | margin-bottom: 1em; 28 | position: relative; 29 | white-space:nowrap; 30 | font-size:1em; 31 | } 32 | #map { 33 | border-radius:.125em; 34 | border:2px solid #1978cf; 35 | margin: 4px 0; 36 | float:left; 37 | width:600px; 38 | height:400px; 39 | } 40 | ul { 41 | font-size:.85em; 42 | margin:0; 43 | padding:0; 44 | } 45 | li { 46 | margin:0 0 2px 18px; 47 | } 48 | #post-it { 49 | width:9em; 50 | height:9em; 51 | margin-left:2em; 52 | padding:1em; 53 | float:left; 54 | background:#fbf5bf; 55 | border:1px solid #c6bb58; 56 | box-shadow: 2px 2px 6px #999; 57 | color:#666; 58 | } 59 | #copy { 60 | position:fixed; 61 | z-index:1000; 62 | right:150px; 63 | top:-8px; 64 | font-size:.85em; 65 | padding:8px 8px 2px 8px; 66 | background: #323b44; 67 | border: 2px solid #737c85; 68 | border-radius:.7em; 69 | opacity: 0.9; 70 | box-shadow:0 0 8px #5f7182; 71 | color:#eee 72 | } 73 | #copy a { 74 | color:#ccc; 75 | text-decoration:none 76 | } 77 | #copy a:hover { 78 | color:#fff 79 | } 80 | #ribbon { 81 | position: absolute; 82 | top: 0; 83 | right: 0; 84 | border: 0; 85 | filter: alpha(opacity=80); 86 | -khtml-opacity: .8; 87 | -moz-opacity: .8; 88 | opacity: .8; 89 | } 90 | .contents { 91 | float:left; 92 | margin:0 2em 2em 0; 93 | } 94 | #comments { 95 | clear:both; 96 | } 97 | 98 | -------------------------------------------------------------------------------- /examples/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 25 | 26 | 27 | 28 |

Leaflet.Control.Search

29 |

Testing area

30 | 31 | 32 | 33 | 34 | 35 |
36 |

search in layer

37 |
38 | 84 |
85 | 86 |
87 |

search in GeoJSON

88 |
89 | 90 | 118 |
119 | 120 |
121 |

Ajax

122 |
123 | 133 |
134 | 135 |
136 |

JSONP

137 |
138 | 148 |
149 | 150 |
151 |

sourceData AJAX(by jQuery)

152 |
153 | 173 |
174 | 175 |
176 |

jsonpUrl, formatData, Cities

177 |
178 | 219 |
220 | 221 | 222 |
223 |

search in layer, Custom Tip

224 |
225 | 249 |
250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /images/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/images/back.png -------------------------------------------------------------------------------- /images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/images/favicon.png -------------------------------------------------------------------------------- /images/leaflet-search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/images/leaflet-search.jpg -------------------------------------------------------------------------------- /images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/images/loader.gif -------------------------------------------------------------------------------- /images/search-icon-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/images/search-icon-mobile.png -------------------------------------------------------------------------------- /images/search-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanocudini/leaflet-search/d29c5392b33cc31b338c3d17b5f8532fef5eae48/images/search-icon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet.Control.Search 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Leaflet.Control.Search

14 | 15 |
16 | A Leaflet Control for search markers/features location by attribute
17 | and much more. 18 |
19 | 20 |
21 |
22 | Other useful stuff for Web Mapping... 23 |
24 | 25 |
26 | If any of these open source solutions help your work and saved you time consider sending a donation 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |

Features

40 |
    41 |
  • Autocomplete
  • 42 |
  • No require external Ajax libs
  • 43 |
  • Retrieve data locations by Ajax/Jsonp
  • 44 |
  • Pre-filtering data from Ajax/Jsonp
  • 45 |
  • Complete fields remapping for remote Jsonp service
  • 46 |
  • Data source callback support
  • 47 |
  • Localization placeholder and text alert
  • 48 |
  • Autozoom on location founded
  • 49 |
  • Autoresize textbox
  • 50 |
  • Customize tooltip menu
  • 51 |
  • Many options to customize the behavior
  • 52 |
  • Support search in features collection
  • 53 |
  • Render Search Box Outside the Leaflet Map
  • 54 |
  • AMD and CommonJS compatible
  • 55 |
56 |
57 | 80 |
81 |

Code repositories

82 | Github.com 83 |
84 | Node Packaged Module 85 |
86 |

Website

87 | opengeo.tech/maps/leaflet-search 88 |
89 | 90 |

Download

91 | 96 |
97 | 98 | 99 | 100 | Fork me on GitHub 101 | 102 |
103 | Publish your application: 104 | Websites that use Leaflet Control Search 105 |
106 |
107 | For questions and bugs: I recommend you to create New Issue on Github repository.
108 | Or to obtain a fast response consult Official Leaflet community forum.
109 |
110 | 111 |
112 |
113 |
114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2023 Stefano Cudini 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-search", 3 | "version": "4.0.0", 4 | "description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:stefanocudini/leaflet-search.git" 8 | }, 9 | "homepage": "https://opengeo.tech/maps/leaflet-search/", 10 | "author": { 11 | "name": "Stefano Cudini", 12 | "email": "stefano.cudini@gmail.com", 13 | "url": "https://opengeo.tech/" 14 | }, 15 | "main": "dist/leaflet-search.src.js", 16 | "style": "dist/leaflet-search.src.css", 17 | "license": "MIT", 18 | "keywords": [ 19 | "gis", 20 | "map", 21 | "leaflet" 22 | ], 23 | "scripts": {}, 24 | "dependencies": { 25 | "leaflet": "*" 26 | }, 27 | "devDependencies": { 28 | "grunt": "^1.4.1", 29 | "grunt-banner": "^0.6.0", 30 | "grunt-cli": "^1.4.3", 31 | "grunt-contrib-clean": "^2.0.0", 32 | "grunt-contrib-concat": "^2.0.0", 33 | "grunt-contrib-cssmin": "^4.0.0", 34 | "grunt-contrib-jshint": "^3.2.0", 35 | "grunt-contrib-uglify": "^5.0.1", 36 | "grunt-contrib-watch": "^1.1.0", 37 | "grunt-remove-logging": "^0.2.0", 38 | "standard": "^17.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/leaflet-search-geocoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | this file is work in progress and represent an extension 3 | of the main plugin to support programmatically most famous geocoder services 4 | 5 | the base idea is: 6 | - any geocoder services is identified by name passed to the plugin option 7 | - any geocoder sub module implemnt custom parameters anc a custom callback to extract resulta in leaflet search format result 8 | - any geocoder accept only two parameters, api key and user key, passed to remote service 9 | 10 | any contributions is welcome <3 11 | 12 | */ 13 | (function (factory) { 14 | // eslint-disable-next-line 15 | if (typeof define === 'function' && define.amd) { 16 | // AMD 17 | // eslint-disable-next-line 18 | define(['leaflet'], factory) 19 | } else if (typeof module !== 'undefined') { 20 | // Node/CommonJS 21 | module.exports = factory(require('leaflet')) 22 | } else { 23 | // Browser globals 24 | if (typeof window.L === 'undefined') { throw new Error('Leaflet must be loaded first') } 25 | factory(window.L) 26 | } 27 | })(function (L) { 28 | L.Control.Search.include({ 29 | options: { 30 | geocoder: 'google', 31 | markerLocation: true, 32 | autoType: false, 33 | autoCollapse: true, 34 | minLength: 2 35 | }, 36 | /* onAdd: function (map) { 37 | L.Control.Search.prototype.onAdd.call(this, map); 38 | console.log('Geocoder',this.options) 39 | }, */ 40 | geocoders: { 41 | /* 42 | 'google': { 43 | urlTmpl: "//maps.googleapis.com/maps/api/geocode/json?key={key}&address={text}" 44 | //todo others 45 | }, 46 | 'here': { 47 | urlTmpl: https://geocoder.ls.hereapi.com/6.2/geocode.json?apiKey={apiKey}&searchtext={text}" 48 | params: function(opts, text) { 49 | 50 | //opts is leaflet options input 51 | //text is input text searched 52 | 53 | return { 54 | 'apiKey': opts.apikey, 55 | 'format': 'json', 56 | 'q': text, 57 | 'jsonp': 'herejsoncallback', 58 | }; 59 | }, 60 | callback: function(resp) { 61 | //TODO refact resp data 62 | } 63 | 64 | "//nominatim.openstreetmap.org/search?" 65 | } */ 66 | } 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /src/leaflet-search.css: -------------------------------------------------------------------------------- 1 | 2 | .leaflet-container .leaflet-control-search { 3 | position:relative; 4 | float:left; 5 | background:#fff; 6 | color:#1978cf; 7 | border: 2px solid rgba(0,0,0,0.2); 8 | background-clip: padding-box; 9 | -moz-border-radius: 4px; 10 | -webkit-border-radius: 4px; 11 | border-radius: 4px; 12 | background-color: rgba(255, 255, 255, 0.8); 13 | z-index:1000; 14 | margin-left: 10px; 15 | margin-top: 10px; 16 | } 17 | .leaflet-control-search.search-exp {/*expanded*/ 18 | background: #fff; 19 | border: 2px solid rgba(0,0,0,0.2); 20 | background-clip: padding-box; 21 | } 22 | .leaflet-control-search .search-input { 23 | display:block; 24 | float:left; 25 | background: #fff; 26 | border:1px solid #666; 27 | border-radius:2px; 28 | height:22px; 29 | padding:0 20px 0 2px; 30 | margin:4px 0 4px 4px; 31 | } 32 | .leaflet-control-search.search-load .search-input { 33 | background: url('../images/loader.gif') no-repeat center right #fff; 34 | } 35 | .leaflet-control-search.search-load .search-cancel { 36 | visibility:hidden; 37 | } 38 | .leaflet-control-search .search-cancel { 39 | display:block; 40 | width:22px; 41 | height:22px; 42 | position:absolute; 43 | right:28px; 44 | margin:6px 0; 45 | background: url('../images/search-icon.png') no-repeat 0 -46px; 46 | text-decoration:none; 47 | filter: alpha(opacity=80); 48 | opacity: 0.8; 49 | } 50 | .leaflet-control-search .search-cancel:hover { 51 | filter: alpha(opacity=100); 52 | opacity: 1; 53 | } 54 | .leaflet-control-search .search-cancel span { 55 | display:none;/* comment for cancel button imageless */ 56 | font-size:18px; 57 | line-height:20px; 58 | color:#ccc; 59 | font-weight:bold; 60 | } 61 | .leaflet-control-search .search-cancel:hover span { 62 | color:#aaa; 63 | } 64 | .leaflet-control-search .search-button { 65 | display:block; 66 | float:left; 67 | width:30px; 68 | height:30px; 69 | background: url('../images/search-icon.png') no-repeat 4px 4px #fff; 70 | border-radius:4px; 71 | } 72 | .leaflet-control-search .search-button:hover { 73 | background: url('../images/search-icon.png') no-repeat 4px -20px #fafafa; 74 | } 75 | .leaflet-control-search .search-tooltip { 76 | position:absolute; 77 | top:100%; 78 | left:0; 79 | float:left; 80 | list-style: none; 81 | padding-left: 0; 82 | min-width:120px; 83 | max-height:122px; 84 | box-shadow: 1px 1px 6px rgba(0,0,0,0.4); 85 | background-color: rgba(0, 0, 0, 0.25); 86 | z-index:1010; 87 | overflow-y:auto; 88 | overflow-x:hidden; 89 | cursor: pointer; 90 | } 91 | .leaflet-control-search .search-tip { 92 | margin:2px; 93 | padding:2px 4px; 94 | display:block; 95 | color:black; 96 | background: #eee; 97 | border-radius:.25em; 98 | text-decoration:none; 99 | white-space:nowrap; 100 | vertical-align:middle; 101 | } 102 | .leaflet-control-search .search-button:hover { 103 | background-color: #f4f4f4; 104 | } 105 | .leaflet-control-search .search-tip-select, 106 | .leaflet-control-search .search-tip:hover { 107 | background-color: #fff; 108 | } 109 | .leaflet-control-search .search-alert { 110 | cursor:pointer; 111 | clear:both; 112 | font-size:.75em; 113 | margin-bottom:5px; 114 | padding:0 .25em; 115 | color:#e00; 116 | font-weight:bold; 117 | border-radius:.25em; 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/leaflet-search.js: -------------------------------------------------------------------------------- 1 | /* 2 | Name Data passed Description 3 | 4 | Managed Events: 5 | search:locationfound {latlng, title, layer} fired after moved and show markerLocation 6 | search:expanded {} fired after control was expanded 7 | search:collapsed {} fired after control was collapsed 8 | search:cancel {} fired after cancel button clicked 9 | 10 | Public methods: 11 | setLayer() L.LayerGroup() set layer search at runtime 12 | showAlert() 'Text message' show alert message 13 | searchText() 'Text searched' search text by external code 14 | */ 15 | 16 | // TODO implement can do research on multiple sources layers and remote 17 | // TODO history: false, //show latest searches in tooltip 18 | // FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location 19 | // FIXME option condition problem {autoCollapse: false } 20 | // 21 | // TODO here insert function search inputText FIRST in _recordsCache keys and if not find results.. 22 | // run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip 23 | // 24 | // TODO change structure of _recordsCache 25 | // like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...} 26 | // in this mode every record can have a free structure of attributes, only 'loc' is required 27 | // TODO important optimization!!! always append data in this._recordsCache 28 | // now _recordsCache content is emptied and replaced with new data founded 29 | // always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch! 30 | // 31 | // TODO here insert function search inputText FIRST in _recordsCache keys and if not find results.. 32 | // run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip 33 | // 34 | // TODO change structure of _recordsCache 35 | // like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...} 36 | // in this way every record can have a free structure of attributes, only 'loc' is required 37 | 38 | (function (factory) { 39 | // eslint-disable-next-line 40 | if (typeof define === 'function' && define.amd) { 41 | // AMD 42 | // eslint-disable-next-line 43 | define(['leaflet'], factory) 44 | } else if (typeof module !== 'undefined') { 45 | // Node/CommonJS 46 | module.exports = factory(require('leaflet')) 47 | } else { 48 | // Browser globals 49 | if (typeof window.L === 'undefined') { throw new Error('Leaflet must be loaded first') } 50 | factory(window.L) 51 | } 52 | })(function (L) { 53 | L.Control.Search = L.Control.extend({ 54 | 55 | includes: L.version[0] === '1' ? L.Evented.prototype : L.Mixin.Events, 56 | 57 | options: { 58 | url: '', // url for search by ajax request, ex: "search.php?q={s}". Can be function to returns string for dynamic parameter setting 59 | layer: null, // layer where search markers(is a L.LayerGroup) 60 | sourceData: null, // function to fill _recordsCache, passed searching text by first param and callback in second 61 | // TODO implements uniq option 'sourceData' to recognizes source type: url,array,callback or layer 62 | jsonpParam: null, // jsonp param name for search by jsonp service, ex: "callback" 63 | propertyLoc: 'loc', // field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) support dotted format: 'prop.subprop.title' 64 | propertyName: 'title', // property in marker.options(or feature.properties for vector layer) trough filter elements in layer, 65 | formatData: null, // callback for reformat all data from source to indexed data object 66 | filterData: null, // callback for filtering data from text searched, params: textSearch, allRecords 67 | moveToLocation: null, // callback run on location found, params: latlng, title, map 68 | buildTip: null, // function to return row tip html node(or html string), receive text tooltip in first param 69 | container: '', // container id to insert Search Control 70 | zoom: null, // default zoom level for move to location 71 | minLength: 1, // minimal text length for autocomplete 72 | initial: true, // search elements only by initial text 73 | casesensitive: false, // search elements in case sensitive text 74 | autoType: true, // complete input with first suggested result and select this filled-in text. 75 | delayType: 400, // delay while typing for show tooltip 76 | tooltipLimit: -1, // limit max results to show in tooltip. -1 for no limit, 0 for no results 77 | tipAutoSubmit: true, // auto map panTo when click on tooltip 78 | firstTipSubmit: false, // auto select first result con enter click 79 | autoResize: true, // autoresize on input change 80 | collapsed: true, // collapse search control at startup 81 | autoCollapse: false, // collapse search control after submit(on button or on tips if enabled tipAutoSubmit) 82 | autoCollapseTime: 1200, // delay for autoclosing alert and collapse after blur 83 | textErr: 'Location not found', // error message 84 | textCancel: 'Cancel', // title in cancel button 85 | textPlaceholder: 'Search...', // placeholder value 86 | hideMarkerOnCollapse: false, // remove circle and marker on search control collapsed 87 | position: 'topleft', 88 | marker: { // custom L.Marker or false for hide 89 | icon: false, // custom L.Icon for maker location or false for hide 90 | animate: true, // animate a circle over location found 91 | circle: { // draw a circle in location found 92 | radius: 10, 93 | weight: 3, 94 | color: '#e03', 95 | stroke: true, 96 | fill: false 97 | } 98 | } 99 | }, 100 | 101 | _getPath: function (obj, prop) { 102 | const parts = prop.split('.') 103 | const last = parts.pop() 104 | const len = parts.length 105 | let cur = parts[0] 106 | let i = 1 107 | 108 | if (len > 0) { 109 | while ((obj = obj[cur]) && i < len) { cur = parts[i++] } 110 | } 111 | 112 | if (obj) { return obj[last] } 113 | }, 114 | 115 | _isObject: function (obj) { 116 | return Object.prototype.toString.call(obj) === '[object Object]' 117 | }, 118 | 119 | initialize: function (options) { 120 | L.Util.setOptions(this, options || {}) 121 | this._inputMinSize = this.options.textPlaceholder ? this.options.textPlaceholder.length : 10 122 | this._layer = this.options.layer || new L.LayerGroup() 123 | this._filterData = this.options.filterData || this._defaultFilterData 124 | this._formatData = this.options.formatData || this._defaultFormatData 125 | this._moveToLocation = this.options.moveToLocation || this._defaultMoveToLocation 126 | this._autoTypeTmp = this.options.autoType // useful for disable autoType temporarily in delete/backspace keydown 127 | this._countertips = 0 // number of tips items 128 | this._recordsCache = {} // key,value table! to store locations! format: key,latlng 129 | this._curReq = null 130 | }, 131 | 132 | onAdd: function (map) { 133 | this._map = map 134 | this._container = L.DomUtil.create('div', 'leaflet-control-search') 135 | this._input = this._createInput(this.options.textPlaceholder, 'search-input') 136 | this._tooltip = this._createTooltip('search-tooltip') 137 | this._cancel = this._createCancel(this.options.textCancel, 'search-cancel') 138 | this._button = this._createButton(this.options.textPlaceholder, 'search-button') 139 | this._alert = this._createAlert('search-alert') 140 | 141 | if (this.options.collapsed === false) { this.expand(this.options.collapsed) } 142 | 143 | if (this.options.marker) { 144 | if (this.options.marker instanceof L.Marker || this.options.marker instanceof L.CircleMarker) { this._markerSearch = this.options.marker } else if (this._isObject(this.options.marker)) { this._markerSearch = new L.Control.Search.Marker([0, 0], this.options.marker) } 145 | 146 | this._markerSearch._isMarkerSearch = true 147 | } 148 | 149 | this.setLayer(this._layer) 150 | 151 | map.on({ 152 | // 'layeradd': this._onLayerAddRemove, 153 | // 'layerremove': this._onLayerAddRemove 154 | resize: this._handleAutoresize 155 | }, this) 156 | return this._container 157 | }, 158 | addTo: function (map) { 159 | if (this.options.container) { 160 | this._container = this.onAdd(map) 161 | this._wrapper = L.DomUtil.get(this.options.container) 162 | this._wrapper.style.position = 'relative' 163 | this._wrapper.appendChild(this._container) 164 | } else { L.Control.prototype.addTo.call(this, map) } 165 | 166 | return this 167 | }, 168 | 169 | onRemove: function (map) { 170 | this._recordsCache = {} 171 | // map.off({ 172 | // 'layeradd': this._onLayerAddRemove, 173 | // 'layerremove': this._onLayerAddRemove 174 | // }, this); 175 | map.off({ 176 | // 'layeradd': this._onLayerAddRemove, 177 | // 'layerremove': this._onLayerAddRemove 178 | resize: this._handleAutoresize 179 | }, this) 180 | }, 181 | 182 | // _onLayerAddRemove: function(e) { 183 | // //without this, run setLayer also for each Markers!! to optimize! 184 | // if(e.layer instanceof L.LayerGroup) 185 | // if( L.stamp(e.layer) != L.stamp(this._layer) ) 186 | // this.setLayer(e.layer); 187 | // }, 188 | 189 | setLayer: function (layer) { // set search layer at runtime 190 | // this.options.layer = layer; //setting this, run only this._recordsFromLayer() 191 | this._layer = layer 192 | this._layer.addTo(this._map) 193 | return this 194 | }, 195 | 196 | showAlert: function (text) { 197 | const self = this 198 | text = text || this.options.textErr 199 | this._alert.style.display = 'block' 200 | this._alert.innerHTML = text 201 | clearTimeout(this.timerAlert) 202 | 203 | this.timerAlert = setTimeout(function () { 204 | self.hideAlert() 205 | }, this.options.autoCollapseTime) 206 | return this 207 | }, 208 | 209 | hideAlert: function () { 210 | this._alert.style.display = 'none' 211 | return this 212 | }, 213 | 214 | cancel: function () { 215 | this._input.value = '' 216 | this._handleKeypress({ keyCode: 8 })// simulate backspace keypress 217 | this._input.size = this._inputMinSize 218 | this._input.focus() 219 | this._cancel.style.display = 'none' 220 | this._hideTooltip() 221 | this.fire('search:cancel') 222 | return this 223 | }, 224 | 225 | expand: function (toggle) { 226 | toggle = typeof toggle === 'boolean' ? toggle : true 227 | this._input.style.display = 'block' 228 | L.DomUtil.addClass(this._container, 'search-exp') 229 | if (toggle !== false) { 230 | this._input.focus() 231 | this._map.on('dragstart click', this.collapse, this) 232 | } 233 | this.fire('search:expanded') 234 | return this 235 | }, 236 | 237 | collapse: function () { 238 | this._hideTooltip() 239 | this.cancel() 240 | this._alert.style.display = 'none' 241 | this._input.blur() 242 | if (this.options.collapsed) { 243 | this._input.style.display = 'none' 244 | this._cancel.style.display = 'none' 245 | L.DomUtil.removeClass(this._container, 'search-exp') 246 | if (this.options.hideMarkerOnCollapse) { 247 | this._map.removeLayer(this._markerSearch) 248 | } 249 | this._map.off('dragstart click', this.collapse, this) 250 | } 251 | this.fire('search:collapsed') 252 | return this 253 | }, 254 | 255 | collapseDelayed: function () { // collapse after delay, used on_input blur 256 | const self = this 257 | if (!this.options.autoCollapse) return this 258 | clearTimeout(this.timerCollapse) 259 | this.timerCollapse = setTimeout(function () { 260 | self.collapse() 261 | }, this.options.autoCollapseTime) 262 | return this 263 | }, 264 | 265 | collapseDelayedStop: function () { 266 | clearTimeout(this.timerCollapse) 267 | return this 268 | }, 269 | 270 | /// /start DOM creations 271 | _createAlert: function (className) { 272 | const alert = L.DomUtil.create('div', className, this._container) 273 | alert.style.display = 'none' 274 | 275 | L.DomEvent 276 | .on(alert, 'click', L.DomEvent.stop, this) 277 | .on(alert, 'click', this.hideAlert, this) 278 | 279 | return alert 280 | }, 281 | 282 | _createInput: function (text, className) { 283 | const self = this 284 | const label = L.DomUtil.create('label', className, this._container) 285 | const input = L.DomUtil.create('input', className, this._container) 286 | input.type = 'text' 287 | input.size = this._inputMinSize 288 | input.value = '' 289 | input.autocomplete = 'off' 290 | input.autocorrect = 'off' 291 | input.autocapitalize = 'off' 292 | input.placeholder = text 293 | input.style.display = 'none' 294 | input.role = 'search' 295 | input.id = input.role + input.type + input.size 296 | 297 | label.htmlFor = input.id 298 | label.style.display = 'none' 299 | label.value = text 300 | 301 | L.DomEvent 302 | .disableClickPropagation(input) 303 | .on(input, 'keyup', this._handleKeypress, this) 304 | .on(input, 'paste', function (e) { 305 | setTimeout(function (e) { 306 | self._handleKeypress(e) 307 | }, 10, e) 308 | }, this) 309 | .on(input, 'blur', this.collapseDelayed, this) 310 | .on(input, 'focus', this.collapseDelayedStop, this) 311 | 312 | return input 313 | }, 314 | 315 | _createCancel: function (title, className) { 316 | const cancel = L.DomUtil.create('a', className, this._container) 317 | cancel.href = '#' 318 | cancel.title = title 319 | cancel.style.display = 'none' 320 | cancel.innerHTML = ''// imageless(see css) 321 | 322 | L.DomEvent 323 | .on(cancel, 'click', L.DomEvent.stop, this) 324 | .on(cancel, 'click', this.cancel, this) 325 | 326 | return cancel 327 | }, 328 | 329 | _createButton: function (title, className) { 330 | const button = L.DomUtil.create('a', className, this._container) 331 | button.href = '#' 332 | button.title = title 333 | 334 | L.DomEvent 335 | .on(button, 'click', L.DomEvent.stop, this) 336 | .on(button, 'click', this._handleSubmit, this) 337 | .on(button, 'focus', this.collapseDelayedStop, this) 338 | .on(button, 'blur', this.collapseDelayed, this) 339 | 340 | return button 341 | }, 342 | 343 | _createTooltip: function (className) { 344 | const self = this 345 | const tool = L.DomUtil.create('ul', className, this._container) 346 | tool.style.display = 'none' 347 | L.DomEvent 348 | .disableClickPropagation(tool) 349 | .on(tool, 'blur', this.collapseDelayed, this) 350 | .on(tool, 'wheel', function (e) { 351 | self.collapseDelayedStop() 352 | L.DomEvent.stopPropagation(e)// disable zoom map 353 | }, this) 354 | .on(tool, 'mouseover', function (e) { 355 | self.collapseDelayedStop() 356 | }, this) 357 | return tool 358 | }, 359 | 360 | _createTip: function (text, val) { // val is object in recordCache, usually is Latlng 361 | let tip 362 | 363 | if (this.options.buildTip) { 364 | tip = this.options.buildTip.call(this, text, val) // custom tip node or html string 365 | if (typeof tip === 'string') { 366 | const tmpNode = L.DomUtil.create('div') 367 | tmpNode.innerHTML = tip 368 | tip = tmpNode.firstChild 369 | } 370 | } else { 371 | tip = L.DomUtil.create('li', '') 372 | tip.innerHTML = text 373 | } 374 | 375 | L.DomUtil.addClass(tip, 'search-tip') 376 | tip._text = text // value replaced in this._input and used by _autoType 377 | 378 | if (this.options.tipAutoSubmit) { 379 | L.DomEvent 380 | .disableClickPropagation(tip) 381 | .on(tip, 'click', L.DomEvent.stop, this) 382 | .on(tip, 'click', function (e) { 383 | this._input.value = text 384 | this._handleAutoresize() 385 | this._input.focus() 386 | this._hideTooltip() 387 | this._handleSubmit() 388 | }, this) 389 | } 390 | 391 | return tip 392 | }, 393 | 394 | /// ///end DOM creations 395 | 396 | _getUrl: function (text) { 397 | return (typeof this.options.url === 'function') ? this.options.url(text) : this.options.url 398 | }, 399 | 400 | _defaultFilterData: function (text, records) { 401 | const frecords = {} 402 | 403 | text = text.replace(new RegExp('[.*+?^${}()|[\]\\]','g'), '') 404 | // sanitize remove all special characters 405 | 406 | if (text === '') { 407 | return [] 408 | } 409 | 410 | const init = this.options.initial ? '^' : '' 411 | const icase = !this.options.casesensitive ? 'i' : undefined 412 | 413 | const regSearch = new RegExp(init + text, icase) 414 | 415 | for (const key in records) { 416 | if (regSearch.test(key)) { 417 | frecords[key] = records[key] 418 | } 419 | } 420 | 421 | return frecords 422 | }, 423 | 424 | showTooltip: function (records) { 425 | this._countertips = 0 426 | this._tooltip.innerHTML = '' 427 | this._tooltip.currentSelection = -1 // inizialized for _handleArrowSelect() 428 | 429 | if (this.options.tooltipLimit) { 430 | for (const key in records) { // fill tooltip 431 | if (this._countertips === this.options.tooltipLimit) { 432 | break 433 | } 434 | 435 | this._countertips++ 436 | 437 | this._tooltip.appendChild(this._createTip(key, records[key])) 438 | } 439 | } 440 | 441 | if (this._countertips > 0) { 442 | this._tooltip.style.display = 'block' 443 | 444 | if (this._autoTypeTmp) { 445 | this._autoType() 446 | } 447 | 448 | this._autoTypeTmp = this.options.autoType// reset default value 449 | } else { 450 | this._hideTooltip() 451 | } 452 | 453 | this._tooltip.scrollTop = 0 454 | 455 | return this._countertips 456 | }, 457 | 458 | _hideTooltip: function () { 459 | this._tooltip.style.display = 'none' 460 | this._tooltip.innerHTML = '' 461 | return 0 462 | }, 463 | 464 | _defaultFormatData: function (json) { // default callback for format data to indexed data 465 | const self = this 466 | const propName = this.options.propertyName 467 | const propLoc = this.options.propertyLoc 468 | const jsonret = {} 469 | 470 | if (L.Util.isArray(propLoc)) { 471 | for (const i in json) { 472 | jsonret[self._getPath(json[i], propName)] = L.latLng(self._getPath(json[i], propLoc[0]), self._getPath(json[i], propLoc[1])) 473 | } 474 | } else { 475 | for (const i in json) { 476 | jsonret[self._getPath(json[i], propName)] = L.latLng(self._getPath(json[i], propLoc)) 477 | } 478 | } 479 | // TODO throw new Error("propertyName '"+propName+"' not found in JSON data"); 480 | return jsonret 481 | }, 482 | 483 | _recordsFromJsonp: function (text, callAfter) { // extract searched records from remote jsonp service 484 | L.Control.Search.callJsonp = callAfter 485 | const script = L.DomUtil.create('script', 'leaflet-search-jsonp', document.getElementsByTagName('body')[0]) 486 | const url = L.Util.template(this._getUrl(text) + '&' + this.options.jsonpParam + '=L.Control.Search.callJsonp', { s: text }) // parsing url 487 | // rnd = '&_='+Math.floor(Math.random()*10000); 488 | // TODO add rnd param or randomize callback name! in recordsFromJsonp 489 | script.type = 'text/javascript' 490 | script.src = url 491 | return { abort: function () { script.parentNode.removeChild(script) } } 492 | }, 493 | 494 | _recordsFromAjax: function (text, callAfter) { // Ajax request 495 | /* 496 | if (window.XMLHttpRequest === undefined) { 497 | window.XMLHttpRequest = function () { 498 | try { 499 | return new ActiveXObject('Microsoft.XMLHTTP.6.0') 500 | } catch (e1) { 501 | try { 502 | return new ActiveXObject('Microsoft.XMLHTTP.3.0') 503 | } catch (e2) { 504 | throw new Error('XMLHttpRequest is not supported') 505 | } 506 | } 507 | } 508 | } 509 | const IE8or9 = (L.Browser.ie && !window.atob && document.querySelector) 510 | const request = IE8or9 ? new XDomainRequest() : new XMLHttpRequest() 511 | */ 512 | let request 513 | 514 | try { 515 | request = new window.XMLHttpRequest() 516 | } catch (e) { 517 | throw new Error('XMLHttpRequest is not supported') 518 | } 519 | const url = L.Util.template(this._getUrl(text), { s: text }) 520 | 521 | // rnd = '&_='+Math.floor(Math.random()*10000); 522 | // TODO add rnd param or randomize callback name! in recordsFromAjax 523 | 524 | request.open('GET', url) 525 | 526 | request.onload = function () { 527 | callAfter(JSON.parse(request.responseText)) 528 | } 529 | request.onreadystatechange = function () { 530 | if (request.readyState === 4 && request.status === 200) { 531 | this.onload() 532 | } 533 | } 534 | 535 | request.send() 536 | return request 537 | }, 538 | 539 | _searchInLayer: function (layer, retRecords, propName, baseProp = 'options') { 540 | const self = this; let loc 541 | 542 | if (layer instanceof L.Control.Search.Marker) return 543 | 544 | if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { 545 | if (self._getPath(layer.options, propName)) { 546 | loc = layer.getLatLng() 547 | loc.layer = layer 548 | retRecords[self._getPath(layer.options, propName)] = loc 549 | } else if (self._getPath(layer.feature.properties, propName)) { 550 | loc = layer.getLatLng() 551 | loc.layer = layer 552 | retRecords[self._getPath(layer.feature.properties, propName)] = loc 553 | } else { 554 | console.warn(`propertyName '${propName}' not found in marker`); 555 | } 556 | } else if (layer instanceof L.Path || layer instanceof L.Polyline || layer instanceof L.Polygon) { 557 | if (self._getPath(layer.options, propName)) { 558 | loc = layer.getBounds().getCenter() 559 | loc.layer = layer 560 | retRecords[self._getPath(layer.options, propName)] = loc 561 | } else if (self._getPath(layer.feature.properties, propName)) { 562 | loc = layer.getBounds().getCenter() 563 | loc.layer = layer 564 | retRecords[self._getPath(layer.feature.properties, propName)] = loc 565 | } else { 566 | console.warn(`propertyName '${propName}' not found in shape`); 567 | } 568 | } else if (Object.prototype.hasOwnProperty.call(layer, 'feature')) { // GeoJSON 569 | if (Object.prototype.hasOwnProperty.call(layer.feature.properties, propName)) { 570 | if (layer.getLatLng && typeof layer.getLatLng === 'function') { 571 | loc = layer.getLatLng() 572 | loc.layer = layer 573 | retRecords[layer.feature.properties[propName]] = loc 574 | } else if (layer.getBounds && typeof layer.getBounds === 'function') { 575 | loc = layer.getBounds().getCenter() 576 | loc.layer = layer 577 | retRecords[layer.feature.properties[propName]] = loc 578 | } else { 579 | console.warn(`Unknown type of Layer`); 580 | } 581 | } else { 582 | console.warn(`propertyName '${propName}' not found in feature`); 583 | } 584 | } else if (layer instanceof L.LayerGroup) { 585 | layer.eachLayer(function (layer) { 586 | self._searchInLayer(layer, retRecords, propName) 587 | }) 588 | } 589 | }, 590 | 591 | _recordsFromLayer: function () { // return table: key,value from layer 592 | const self = this 593 | const retRecords = {} 594 | const propName = this.options.propertyName 595 | 596 | this._layer.eachLayer(function (layer) { 597 | self._searchInLayer(layer, retRecords, propName) 598 | }) 599 | 600 | return retRecords 601 | }, 602 | 603 | _autoType: function () { 604 | // TODO implements autype without selection(useful for mobile device) 605 | 606 | const start = this._input.value.length 607 | const firstRecord = this._tooltip.firstChild ? this._tooltip.firstChild._text : '' 608 | const end = firstRecord.length 609 | 610 | if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match 611 | this._input.value = firstRecord 612 | this._handleAutoresize() 613 | 614 | if (this._input.createTextRange) { 615 | const selRange = this._input.createTextRange() 616 | selRange.collapse(true) 617 | selRange.moveStart('character', start) 618 | selRange.moveEnd('character', end) 619 | selRange.select() 620 | } else if (this._input.setSelectionRange) { 621 | this._input.setSelectionRange(start, end) 622 | } else if (this._input.selectionStart) { 623 | this._input.selectionStart = start 624 | this._input.selectionEnd = end 625 | } 626 | } 627 | }, 628 | 629 | _hideAutoType: function () { // deselect text: 630 | let sel 631 | if ((sel = this._input.selection) && sel.empty) { 632 | sel.empty() 633 | } else if (this._input.createTextRange) { 634 | sel = this._input.createTextRange() 635 | sel.collapse(true) 636 | const end = this._input.value.length 637 | sel.moveStart('character', end) 638 | sel.moveEnd('character', end) 639 | sel.select() 640 | } else { 641 | if (this._input.getSelection) { 642 | this._input.getSelection().removeAllRanges() 643 | } 644 | this._input.selectionStart = this._input.selectionEnd 645 | } 646 | }, 647 | 648 | _handleKeypress: function (e) { // run _input keyup event 649 | const self = this 650 | 651 | switch (e.keyCode) { 652 | case 27: /* Esc */ 653 | this.collapse() 654 | break 655 | case 13: /* Enter */ 656 | if (this._countertips === 1 || (this.options.firstTipSubmit && this._countertips > 0)) { 657 | if (this._tooltip.currentSelection === -1) { 658 | this._handleArrowSelect(1) 659 | } 660 | } 661 | this._handleSubmit() // do search 662 | break 663 | case 38: /* Up */ 664 | this._handleArrowSelect(-1) 665 | break 666 | case 40: /* Down */ 667 | this._handleArrowSelect(1) 668 | break 669 | case 45: /* Insert */ 670 | case 46: /* Delete */ 671 | this._autoTypeTmp = false// disable temporarily autoType 672 | break 673 | case 37: /* Left */ 674 | case 39: /* Right */ 675 | case 16: /* Shift */ 676 | case 17: /* Ctrl */ 677 | case 35: /* End */ 678 | case 36: /* Home */ 679 | break 680 | default: /* All keys */ 681 | if (this._input.value.length) { 682 | this._cancel.style.display = 'block' 683 | } 684 | else { 685 | this._cancel.style.display = 'none' 686 | } 687 | 688 | if (this._input.value.length >= this.options.minLength) { 689 | clearTimeout(this.timerKeypress) // cancel last search request while type in 690 | this.timerKeypress = setTimeout(function () { // delay before request, for limit jsonp/ajax request 691 | self._fillRecordsCache() 692 | }, this.options.delayType) 693 | } else { this._hideTooltip() } 694 | } 695 | 696 | this._handleAutoresize() 697 | }, 698 | 699 | searchText: function (text) { 700 | const code = text.charCodeAt(text.length) 701 | 702 | this._input.value = text 703 | 704 | this._input.style.display = 'block' 705 | L.DomUtil.addClass(this._container, 'search-exp') 706 | 707 | this._autoTypeTmp = false 708 | 709 | this._handleKeypress({ keyCode: code }) 710 | }, 711 | 712 | _fillRecordsCache: function () { 713 | const self = this 714 | const inputText = this._input.value; let records 715 | 716 | if (this._curReq && this._curReq.abort) { this._curReq.abort() } 717 | // abort previous requests 718 | 719 | L.DomUtil.addClass(this._container, 'search-load') 720 | 721 | if (this.options.layer) { 722 | // TODO _recordsFromLayer must return array of objects, formatted from _formatData 723 | this._recordsCache = this._recordsFromLayer() 724 | 725 | records = this._filterData(this._input.value, this._recordsCache) 726 | 727 | this.showTooltip(records) 728 | 729 | L.DomUtil.removeClass(this._container, 'search-load') 730 | } else { 731 | if (this.options.sourceData) { this._retrieveData = this.options.sourceData } else if (this.options.url) { // jsonp or ajax 732 | this._retrieveData = this.options.jsonpParam ? this._recordsFromJsonp : this._recordsFromAjax 733 | } 734 | 735 | this._curReq = this._retrieveData.call(this, inputText, function (data) { 736 | self._recordsCache = self._formatData(self, data) 737 | 738 | // TODO refact! 739 | if (self.options.sourceData) { records = self._filterData(self._input.value, self._recordsCache) } else { records = self._recordsCache } 740 | 741 | self.showTooltip(records) 742 | 743 | L.DomUtil.removeClass(self._container, 'search-load') 744 | }) 745 | } 746 | }, 747 | 748 | _handleAutoresize: function () { 749 | let maxWidth 750 | 751 | if (this._input.style.maxWidth !== this._map._container.offsetWidth) { 752 | maxWidth = this._map._container.clientWidth 753 | 754 | // other side margin + padding + width border + width search-button + width search-cancel 755 | maxWidth -= 10 + 20 + 1 + 30 + 22 756 | 757 | this._input.style.maxWidth = maxWidth.toString() + 'px' 758 | } 759 | 760 | if (this.options.autoResize && (this._container.offsetWidth + 20 < this._map._container.offsetWidth)) { 761 | this._input.size = this._input.value.length < this._inputMinSize ? this._inputMinSize : this._input.value.length 762 | } 763 | }, 764 | 765 | _handleArrowSelect: function (velocity) { 766 | const searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : [] 767 | 768 | for (let i = 0; i < searchTips.length; i++) { 769 | L.DomUtil.removeClass(searchTips[i], 'search-tip-select') 770 | } 771 | 772 | if ((velocity === 1) && (this._tooltip.currentSelection >= (searchTips.length - 1))) { // If at end of list. 773 | L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select') 774 | } else if ((velocity === -1) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box. 775 | this._tooltip.currentSelection = -1 776 | } else if (this._tooltip.style.display !== 'none') { 777 | this._tooltip.currentSelection += velocity 778 | 779 | L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select') 780 | 781 | this._input.value = searchTips[this._tooltip.currentSelection]._text 782 | 783 | // scroll: 784 | const tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop 785 | 786 | if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) { 787 | this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight 788 | } else if (tipOffsetTop <= this._tooltip.scrollTop) { 789 | this._tooltip.scrollTop = tipOffsetTop 790 | } 791 | } 792 | }, 793 | 794 | _handleSubmit: function () { // button and tooltip click and enter submit 795 | this._hideAutoType() 796 | 797 | this.hideAlert() 798 | this._hideTooltip() 799 | 800 | if (this._input.style.display === 'none') { // on first click show _input only 801 | this.expand() 802 | } else { 803 | if (this._input.value === '') { // hide _input only 804 | this.collapse() 805 | } else { 806 | const loc = this._getLocation(this._input.value) 807 | 808 | if (!loc) { 809 | this.showAlert() 810 | } else { 811 | this.showLocation(loc, this._input.value) 812 | this.fire('search:locationfound', { 813 | latlng: loc, 814 | text: this._input.value, 815 | layer: loc.layer ? loc.layer : null 816 | }) 817 | } 818 | } 819 | } 820 | }, 821 | 822 | _getLocation: function (key) { // extract latlng from _recordsCache 823 | if (Object.prototype.hasOwnProperty.call(this._recordsCache, key)) { 824 | return this._recordsCache[key] 825 | } else { 826 | return false 827 | } 828 | }, 829 | 830 | _defaultMoveToLocation: function (latlng, title, map) { 831 | if (this.options.zoom) { 832 | this._map.setView(latlng, this.options.zoom) 833 | } else { 834 | this._map.panTo(latlng) 835 | } 836 | }, 837 | 838 | showLocation: function (latlng, title) { // set location on map from _recordsCache 839 | const self = this 840 | 841 | self._map.once('moveend zoomend', function (e) { 842 | if (self._markerSearch) { 843 | self._markerSearch.addTo(self._map).setLatLng(latlng) 844 | } 845 | }) 846 | 847 | self._moveToLocation(latlng, title, self._map) 848 | // FIXME autoCollapse option hide self._markerSearch before visualized!! 849 | if (self.options.autoCollapse) { self.collapse() } 850 | 851 | return self 852 | } 853 | }) 854 | 855 | L.Control.Search.Marker = L.Marker.extend({ 856 | 857 | includes: L.version[0] === '1' ? L.Evented.prototype : L.Mixin.Events, 858 | 859 | options: { 860 | icon: new L.Icon.Default(), 861 | animate: true, 862 | circle: { 863 | radius: 10, 864 | weight: 3, 865 | color: '#e03', 866 | stroke: true, 867 | fill: false 868 | } 869 | }, 870 | 871 | initialize: function (latlng, options) { 872 | L.setOptions(this, options) 873 | 874 | if (options.icon === true) { options.icon = new L.Icon.Default() } 875 | 876 | L.Marker.prototype.initialize.call(this, latlng, options) 877 | 878 | if (L.Control.Search.prototype._isObject(this.options.circle)) { this._circleLoc = new L.CircleMarker(latlng, this.options.circle) } 879 | }, 880 | 881 | onAdd: function (map) { 882 | L.Marker.prototype.onAdd.call(this, map) 883 | if (this._circleLoc) { 884 | map.addLayer(this._circleLoc) 885 | if (this.options.animate) { this.animate() } 886 | } 887 | }, 888 | 889 | onRemove: function (map) { 890 | L.Marker.prototype.onRemove.call(this, map) 891 | if (this._circleLoc) { map.removeLayer(this._circleLoc) } 892 | }, 893 | 894 | setLatLng: function (latlng) { 895 | L.Marker.prototype.setLatLng.call(this, latlng) 896 | if (this._circleLoc) { this._circleLoc.setLatLng(latlng) } 897 | return this 898 | }, 899 | 900 | _initIcon: function () { 901 | if (this.options.icon) { L.Marker.prototype._initIcon.call(this) } 902 | }, 903 | 904 | _removeIcon: function () { 905 | if (this.options.icon) { L.Marker.prototype._removeIcon.call(this) } 906 | }, 907 | 908 | animate: function () { 909 | // TODO refact animate() more smooth! like this: http://goo.gl/DDlRs 910 | if (this._circleLoc) { 911 | const circle = this._circleLoc 912 | const tInt = 200 // time interval 913 | const ss = 5 // frames 914 | let mr = parseInt(circle._radius / ss) 915 | const oldrad = this.options.circle.radius 916 | let newrad = circle._radius * 2 917 | let acc = 0 918 | 919 | circle._timerAnimLoc = setInterval(function () { 920 | acc += 0.5 921 | mr += acc // adding acceleration 922 | newrad -= mr 923 | 924 | circle.setRadius(newrad) 925 | 926 | if (newrad < oldrad) { 927 | clearInterval(circle._timerAnimLoc) 928 | circle.setRadius(oldrad)// reset radius 929 | // if(typeof afterAnimCall == 'function') 930 | // afterAnimCall(); 931 | // TODO use create event 'animateEnd' in L.Control.Search.Marker 932 | } 933 | }, tInt) 934 | } 935 | 936 | return this 937 | } 938 | }) 939 | 940 | L.Map.addInitHook(function () { 941 | if (this.options.searchControl) { 942 | this.searchControl = L.control.search(this.options.searchControl) 943 | this.addControl(this.searchControl) 944 | } 945 | }) 946 | 947 | L.control.search = function (options) { 948 | return new L.Control.Search(options) 949 | } 950 | 951 | return L.Control.Search 952 | }) 953 | -------------------------------------------------------------------------------- /src/leaflet-search.mobile.css: -------------------------------------------------------------------------------- 1 | 2 | /* SEARCH */ 3 | .leaflet-control.leaflet-control-search { 4 | z-index:2000; 5 | } 6 | .leaflet-control-search .search-input { 7 | display:block; 8 | float:left; 9 | background: #fff; 10 | border:1px solid #666; 11 | border-radius:2px; 12 | height:24px; 13 | font-size:1.25em; 14 | padding:0 .125em; 15 | margin:3px; 16 | padding-right:30px; 17 | } 18 | .leaflet-control-search .search-button:hover, 19 | .leaflet-control-search .search-button { 20 | background-image: url('../images/search-icon-mobile.png'); 21 | -webkit-border-radius: 4px; 22 | border-radius: 4px; 23 | background-position: 1px 1px; 24 | width:32px; 25 | height:32px; 26 | } 27 | .leaflet-control-search.search-load .search-input { 28 | background: url('../images/loader.gif') no-repeat center right #fff; 29 | } 30 | .leaflet-control-search .search-cancel { 31 | background-image: url('../images/search-icon-mobile.png'); 32 | -webkit-border-radius: 4px; 33 | border-radius: 4px; 34 | background-position: 0px -62px; 35 | width:26px; 36 | height:26px; 37 | right:34px; 38 | margin:3px; 39 | } 40 | .leaflet-control-search .search-tooltip { 41 | max-height:142px;/*(.search-tip height * 5)*/ 42 | } 43 | .leaflet-control-search .search-tip { 44 | font-size:1em; 45 | margin:2px; 46 | padding:2px; 47 | display:block; 48 | color:black; 49 | background: rgba(255,255,255,0.8); 50 | border-radius:.25em; 51 | text-decoration:none; 52 | white-space:nowrap; 53 | vertical-align:center; 54 | } 55 | .leaflet-control-search .search-tip .climbo-icon-mini { 56 | float:right; 57 | display:block; 58 | white-space:nowrap; 59 | } 60 | .leaflet-control-search .search-button:hover, 61 | .leaflet-control-search .search-tip-select, 62 | .leaflet-control-search .search-tip:hover { 63 | background-color: #fff; 64 | } 65 | .leaflet-control-search .search-alert { 66 | font-size:1.2em; 67 | } --------------------------------------------------------------------------------