forked from MapComplete/MapComplete
		
	Search: add support for OpenLocationCodes and some other coordinate formats, see #2157
This commit is contained in:
		
							parent
							
								
									181219928c
								
							
						
					
					
						commit
						4d2b3c9cf7
					
				
					 5 changed files with 106 additions and 5 deletions
				
			
		
							
								
								
									
										22
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -31,6 +31,7 @@ | |||
|         "buffer": "^6.0.3", | ||||
|         "chart.js": "^3.8.0", | ||||
|         "comunica": "^2.0.0", | ||||
|         "coordinate-parser": "^1.0.7", | ||||
|         "country-language": "^0.1.7", | ||||
|         "country-to-currency": "^1.0.10", | ||||
|         "crypto": "^1.0.1", | ||||
|  | @ -69,6 +70,7 @@ | |||
|         "papaparse": "^5.3.1", | ||||
|         "pg": "^8.11.3", | ||||
|         "pic4carto": "^2.1.15", | ||||
|         "pluscodes": "^2.6.0", | ||||
|         "pmtiles": "^3.0.5", | ||||
|         "prompt-sync": "^4.2.0", | ||||
|         "qrcode-generator": "^1.4.4", | ||||
|  | @ -8651,6 +8653,11 @@ | |||
|         "monotone-convex-hull-2d": "^1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/coordinate-parser": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/coordinate-parser/-/coordinate-parser-1.0.7.tgz", | ||||
|       "integrity": "sha512-pkcjigkAEjU5JsTYnuXLkRgR6T5fF/7GXR4p9vWJesy8fKwsheN8zC5d3sSvdMmWihHB4u48xWZ5mUCcgBIEpw==" | ||||
|     }, | ||||
|     "node_modules/core-js": { | ||||
|       "version": "2.6.12", | ||||
|       "dev": true, | ||||
|  | @ -16246,6 +16253,11 @@ | |||
|         "pathe": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/pluscodes": { | ||||
|       "version": "2.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/pluscodes/-/pluscodes-2.6.0.tgz", | ||||
|       "integrity": "sha512-+3sW+Qt+znuN2uMFFvebo2m5MsaTjBXOzEYvkfx4RMeOYnNCQv3OWeQujfRAo6nzg7D+5vD2b3tihtwW3b5pfg==" | ||||
|     }, | ||||
|     "node_modules/pmtiles": { | ||||
|       "version": "3.0.5", | ||||
|       "license": "BSD-3-Clause", | ||||
|  | @ -27289,6 +27301,11 @@ | |||
|         "monotone-convex-hull-2d": "^1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "coordinate-parser": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/coordinate-parser/-/coordinate-parser-1.0.7.tgz", | ||||
|       "integrity": "sha512-pkcjigkAEjU5JsTYnuXLkRgR6T5fF/7GXR4p9vWJesy8fKwsheN8zC5d3sSvdMmWihHB4u48xWZ5mUCcgBIEpw==" | ||||
|     }, | ||||
|     "core-js": { | ||||
|       "version": "2.6.12", | ||||
|       "dev": true | ||||
|  | @ -32245,6 +32262,11 @@ | |||
|         "pathe": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "pluscodes": { | ||||
|       "version": "2.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/pluscodes/-/pluscodes-2.6.0.tgz", | ||||
|       "integrity": "sha512-+3sW+Qt+znuN2uMFFvebo2m5MsaTjBXOzEYvkfx4RMeOYnNCQv3OWeQujfRAo6nzg7D+5vD2b3tihtwW3b5pfg==" | ||||
|     }, | ||||
|     "pmtiles": { | ||||
|       "version": "3.0.5", | ||||
|       "requires": { | ||||
|  |  | |||
|  | @ -175,6 +175,7 @@ | |||
|     "buffer": "^6.0.3", | ||||
|     "chart.js": "^3.8.0", | ||||
|     "comunica": "^2.0.0", | ||||
|     "coordinate-parser": "^1.0.7", | ||||
|     "country-language": "^0.1.7", | ||||
|     "country-to-currency": "^1.0.10", | ||||
|     "crypto": "^1.0.1", | ||||
|  | @ -213,6 +214,7 @@ | |||
|     "papaparse": "^5.3.1", | ||||
|     "pg": "^8.11.3", | ||||
|     "pic4carto": "^2.1.15", | ||||
|     "pluscodes": "^2.6.0", | ||||
|     "pmtiles": "^3.0.5", | ||||
|     "prompt-sync": "^4.2.0", | ||||
|     "qrcode-generator": "^1.4.4", | ||||
|  |  | |||
|  | @ -1,13 +1,15 @@ | |||
| import GeocodingProvider, { GeocodeResult } from "./GeocodingProvider" | ||||
| import { Utils } from "../../Utils" | ||||
| import { ImmutableStore, Store } from "../UIEventSource" | ||||
| 
 | ||||
| import CoordinateParser from "coordinate-parser" | ||||
| /** | ||||
|  * A simple search-class which interprets possible locations | ||||
|  */ | ||||
| export default class CoordinateSearch implements GeocodingProvider { | ||||
|     private static readonly latLonRegexes: ReadonlyArray<RegExp> = [ | ||||
|         /^(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/, | ||||
|         /^ *(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/, | ||||
|         /^ *(-?[0-9]+,[0-9]+)[ ;/\\]+(-?[0-9]+,[0-9]+)/, | ||||
| 
 | ||||
|         /lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lon[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/, | ||||
|         /lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lng[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/, | ||||
| 
 | ||||
|  | @ -17,6 +19,8 @@ export default class CoordinateSearch implements GeocodingProvider { | |||
| 
 | ||||
|     private static readonly lonLatRegexes: ReadonlyArray<RegExp> = [ | ||||
|         /^(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/, | ||||
|         /^ *(-?[0-9]+,[0-9]+)[ ;/\\]+(-?[0-9]+,[0-9]+)/, | ||||
| 
 | ||||
|         /lon[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/, | ||||
|         /lng[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/, | ||||
| 
 | ||||
|  | @ -58,14 +62,31 @@ export default class CoordinateSearch implements GeocodingProvider { | |||
|      * const results = ls.directSearch('  lat="-57.5802905" lon="-12.7202538"') | ||||
|      * results.length // => 1
 | ||||
|      * results[0] // => {lat: -57.5802905, lon: -12.7202538, "display_name": "lon: -12.720254, lat: -57.58029",  "category": "coordinate","osm_id": "-12.720254/-57.58029", "source": "coordinate:latlon"}
 | ||||
|      * | ||||
|      * // Should work with commas
 | ||||
|      * const ls = new CoordinateSearch() | ||||
|      * const results = ls.directSearch('51,047977 3,51184') | ||||
|      * results.length // => 2
 | ||||
|      * results[0] // => {lat: 51.047977, lon: 3.51184, "display_name": "lon: 3.51184, lat: 51.047977",  "category": "coordinate","osm_id": "3.51184/51.047977", "source": "coordinate:latlon"}
 | ||||
|      */ | ||||
|     private directSearch(query: string): GeocodeResult[] { | ||||
|         const matches = Utils.NoNull(CoordinateSearch.latLonRegexes.map(r => query.match(r))) | ||||
|             .map(m => CoordinateSearch.asResult(m[2], m[1], "latlon") ) | ||||
|             .map(m => CoordinateSearch.asResult(m[2], m[1], "latlon")) | ||||
| 
 | ||||
|         const matchesLonLat = Utils.NoNull(CoordinateSearch.lonLatRegexes.map(r => query.match(r))) | ||||
|             .map(m => CoordinateSearch.asResult(m[1], m[2], "lonlat")) | ||||
|         return matches.concat(matchesLonLat) | ||||
|         const init = matches.concat(matchesLonLat) | ||||
|         if (init.length > 0) { | ||||
|             return init | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             const c = new CoordinateParser(query); | ||||
|             return [CoordinateSearch.asResult(""+c.getLongitude(), ""+c.getLatitude(), "coordinateParser")] | ||||
|         } catch { | ||||
|             return [] | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static round6(n: number): string { | ||||
|  | @ -73,6 +94,9 @@ export default class CoordinateSearch implements GeocodingProvider { | |||
|     } | ||||
| 
 | ||||
|     private static asResult(lonIn: string, latIn: string, source: string): GeocodeResult { | ||||
|         lonIn = lonIn.replaceAll(",", ".") | ||||
|         latIn = latIn.replaceAll(",", ".") | ||||
| 
 | ||||
|         const lon = Number(lonIn) | ||||
|         const lat = Number(latIn) | ||||
|         const lonStr = CoordinateSearch.round6(lon) | ||||
|  | @ -82,7 +106,7 @@ export default class CoordinateSearch implements GeocodingProvider { | |||
|             lon, | ||||
|             display_name: "lon: " + lonStr + ", lat: " + latStr, | ||||
|             category: "coordinate", | ||||
|             source: "coordinate:"+source, | ||||
|             source: "coordinate:" + source, | ||||
|             osm_id: lonStr + "/" + latStr, | ||||
|         } | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										51
									
								
								src/Logic/Search/OpenLocationCodeSearch.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/Logic/Search/OpenLocationCodeSearch.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| import { Store, Stores, UIEventSource } from "../UIEventSource" | ||||
| import GeocodingProvider, { | ||||
|     GeocodeResult, | ||||
|     GeocodingOptions, | ||||
|     ReverseGeocodingProvider, | ||||
|     ReverseGeocodingResult, | ||||
| } from "./GeocodingProvider" | ||||
| import { decode as pluscode_decode } from "pluscodes" | ||||
| 
 | ||||
| export default class OpenLocationCodeSearch implements GeocodingProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * A regex describing all plus-codes | ||||
|      */ | ||||
|     public static readonly _isPlusCode = /^([2-9CFGHJMPQRVWX]{2}|00){2,4}\+([2-9CFGHJMPQRVWX]{2,3})?$/ | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * OpenLocationCodeSearch.isPlusCode("9FFW84J9+XG") // => true
 | ||||
|      * OpenLocationCodeSearch.isPlusCode("9FFW84J9+") // => true
 | ||||
|      * OpenLocationCodeSearch.isPlusCode("9AFW84J9+") // => false
 | ||||
|      * OpenLocationCodeSearch.isPlusCode("9FFW+") // => true
 | ||||
|      * OpenLocationCodeSearch.isPlusCode("9FFW0000+") // => true
 | ||||
|      * OpenLocationCodeSearch.isPlusCode("9FFw0000+") // => true
 | ||||
|      * OpenLocationCodeSearch.isPlusCode("9FFW000+") // => false
 | ||||
|      * | ||||
|      */ | ||||
|     public static isPlusCode(str: string) { | ||||
|         return str.toUpperCase().match(this._isPlusCode) !== null | ||||
|     } | ||||
| 
 | ||||
|     async search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]> { | ||||
|         if (!OpenLocationCodeSearch.isPlusCode(query)) { | ||||
|             return undefined | ||||
|         } | ||||
|         const { latitude, longitude } = pluscode_decode(query) | ||||
| 
 | ||||
|         return [{ | ||||
|             lon: longitude, | ||||
|             lat: latitude, | ||||
|             description: "Open Location Code", | ||||
|             osm_id: query, | ||||
|             display_name: query.toUpperCase(), | ||||
|         }] | ||||
|     } | ||||
| 
 | ||||
|     suggest?(query: string, options?: GeocodingOptions): Store<GeocodeResult[]> { | ||||
|         return Stores.FromPromise(this.search(query, options)) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -15,6 +15,7 @@ import LayerSearch from "../Search/LayerSearch" | |||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import { FeatureSource } from "../FeatureSource/FeatureSource" | ||||
| import { Feature } from "geojson" | ||||
| import OpenLocationCodeSearch from "../Search/OpenLocationCodeSearch" | ||||
| 
 | ||||
| export default class SearchState { | ||||
| 
 | ||||
|  | @ -38,6 +39,7 @@ export default class SearchState { | |||
|         this.locationSearchers = [ | ||||
|             new LocalElementSearch(state, 5), | ||||
|             new CoordinateSearch(), | ||||
|             new OpenLocationCodeSearch(), | ||||
|             new OpenStreetMapIdSearch(state), | ||||
|             new PhotonSearch(true, 2), | ||||
|             new PhotonSearch(), | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue