| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  | import { Client } from "pg" | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  | import { Tiles } from "../../src/Models/TileRange" | 
					
						
							| 
									
										
										
										
											2024-02-22 11:55:32 +01:00
										 |  |  | import { Server } from "../server" | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Just the OSM2PGSL default database | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | interface PoiDatabaseMeta { | 
					
						
							|  |  |  |     attributes | 
					
						
							|  |  |  |     current_timestamp | 
					
						
							|  |  |  |     db_format | 
					
						
							|  |  |  |     flat_node_file | 
					
						
							|  |  |  |     import_timestamp | 
					
						
							|  |  |  |     output | 
					
						
							|  |  |  |     prefix | 
					
						
							|  |  |  |     replication_base_url | 
					
						
							|  |  |  |     replication_sequence_number | 
					
						
							|  |  |  |     replication_timestamp | 
					
						
							|  |  |  |     style | 
					
						
							|  |  |  |     updatable | 
					
						
							|  |  |  |     version | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Connects with a Postgis database, gives back how much items there are within the given BBOX | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  | class OsmPoiDatabase { | 
					
						
							|  |  |  |     private static readonly prefixes: ReadonlyArray<string> = ["pois", "lines", "polygons"] | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  |     private readonly _client: Client | 
					
						
							|  |  |  |     private isConnected = false | 
					
						
							| 
									
										
										
										
											2024-02-18 15:59:28 +01:00
										 |  |  |     private supportedLayers: Set<string> = undefined | 
					
						
							| 
									
										
										
										
											2024-02-18 17:38:29 +01:00
										 |  |  |     private supportedLayersDate: Date = undefined | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |     private metaCache: PoiDatabaseMeta = undefined | 
					
						
							|  |  |  |     private metaCacheDate: Date = undefined | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor(connectionString: string) { | 
					
						
							|  |  |  |         this._client = new Client(connectionString) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |     async getCount( | 
					
						
							|  |  |  |         layer: string, | 
					
						
							|  |  |  |         bbox: [[number, number], [number, number]] = undefined | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |     ): Promise<{ count: number; lat: number; lon: number }> { | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  |         if (!this.isConnected) { | 
					
						
							|  |  |  |             await this._client.connect() | 
					
						
							|  |  |  |             this.isConnected = true | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |         let total: number = 0 | 
					
						
							|  |  |  |         let latSum = 0 | 
					
						
							|  |  |  |         let lonSum = 0 | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |         for (const prefix of OsmPoiDatabase.prefixes) { | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |             let query = | 
					
						
							|  |  |  |                 "SELECT COUNT(*), ST_AsText(ST_Centroid(ST_Collect(geom))) FROM " + | 
					
						
							|  |  |  |                 prefix + | 
					
						
							|  |  |  |                 "_" + | 
					
						
							|  |  |  |                 layer | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |             if (bbox) { | 
					
						
							|  |  |  |                 query += ` WHERE ST_MakeEnvelope (${bbox[0][0]}, ${bbox[0][1]}, ${bbox[1][0]}, ${bbox[1][1]}, 4326) ~ geom` | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const result = await this._client.query(query) | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |             const count = Number(result.rows[0].count) | 
					
						
							|  |  |  |             let point = result.rows[0].st_astext | 
					
						
							|  |  |  |             if (count === 0) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             total += count | 
					
						
							|  |  |  |             if (!point) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             point = point.substring(6, point.length - 1) | 
					
						
							|  |  |  |             const [lon, lat] = point.split(" ") | 
					
						
							|  |  |  |             latSum += lat * count | 
					
						
							|  |  |  |             lonSum += lon * count | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return { count: total, lat: latSum / total, lon: lonSum / total } | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     disconnect() { | 
					
						
							|  |  |  |         this._client.end() | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-18 15:59:28 +01:00
										 |  |  |     async getLayers(): Promise<Set<string>> { | 
					
						
							| 
									
										
										
										
											2024-02-18 17:38:29 +01:00
										 |  |  |         if ( | 
					
						
							|  |  |  |             this.supportedLayers !== undefined && | 
					
						
							|  |  |  |             new Date().getTime() - this.supportedLayersDate.getTime() < 1000 * 60 * 60 * 24 | 
					
						
							|  |  |  |         ) { | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |             return this.supportedLayers | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-18 17:38:29 +01:00
										 |  |  |         const q = | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |             "SELECT table_name \n" + | 
					
						
							| 
									
										
										
										
											2024-02-18 17:38:29 +01:00
										 |  |  |             "FROM information_schema.tables \n" + | 
					
						
							|  |  |  |             "WHERE table_schema = 'public' AND table_name LIKE 'lines_%';" | 
					
						
							|  |  |  |         const result = await this._client.query(q) | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |         const layers = result.rows.map((r) => r.table_name.substring("lines_".length)) | 
					
						
							| 
									
										
										
										
											2024-02-18 15:59:28 +01:00
										 |  |  |         this.supportedLayers = new Set(layers) | 
					
						
							| 
									
										
										
										
											2024-02-18 17:38:29 +01:00
										 |  |  |         this.supportedLayersDate = new Date() | 
					
						
							| 
									
										
										
										
											2024-02-18 15:59:28 +01:00
										 |  |  |         return this.supportedLayers | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async getMeta(): Promise<PoiDatabaseMeta> { | 
					
						
							|  |  |  |         const now = new Date() | 
					
						
							|  |  |  |         if (this.metaCache !== undefined) { | 
					
						
							|  |  |  |             const diffSec = (this.metaCacheDate.getTime() - now.getTime()) / 1000 | 
					
						
							|  |  |  |             if (diffSec < 120) { | 
					
						
							|  |  |  |                 return this.metaCache | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const result = await this._client.query("SELECT * FROM public.osm2pgsql_properties") | 
					
						
							|  |  |  |         const meta = {} | 
					
						
							|  |  |  |         for (const { property, value } of result.rows) { | 
					
						
							|  |  |  |             meta[property] = value | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.metaCacheDate = now | 
					
						
							|  |  |  |         this.metaCache = <any>meta | 
					
						
							|  |  |  |         return this.metaCache | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-01-22 01:42:05 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  | class CachedSqlCount { | 
					
						
							|  |  |  |     private readonly _cache: Record< | 
					
						
							|  |  |  |         string, | 
					
						
							|  |  |  |         Record< | 
					
						
							|  |  |  |             number, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 date: Date | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |                 entry: { count: number; lat: number; lon: number } | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |     > = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private readonly _poiDatabase: OsmPoiDatabase | 
					
						
							|  |  |  |     private readonly _maxAge: number | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |     constructor(poiDatabase: OsmPoiDatabase, maxAge: number) { | 
					
						
							|  |  |  |         this._poiDatabase = poiDatabase | 
					
						
							|  |  |  |         this._maxAge = maxAge | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |     public async getCount( | 
					
						
							|  |  |  |         layer: string, | 
					
						
							|  |  |  |         tileId: number | 
					
						
							|  |  |  |     ): Promise<{ count: number; lat: number; lon: number }> { | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |         const cachedEntry = this._cache[layer]?.[tileId] | 
					
						
							|  |  |  |         if (cachedEntry) { | 
					
						
							|  |  |  |             const age = (new Date().getTime() - cachedEntry.date.getTime()) / 1000 | 
					
						
							|  |  |  |             if (age < this._maxAge) { | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |                 return cachedEntry.entry | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const bbox = Tiles.tile_bounds_lon_lat(...Tiles.tile_from_index(tileId)) | 
					
						
							|  |  |  |         const count = await this._poiDatabase.getCount(layer, bbox) | 
					
						
							|  |  |  |         if (!this._cache[layer]) { | 
					
						
							|  |  |  |             this._cache[layer] = {} | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |         this._cache[layer][tileId] = { entry: count, date: new Date() } | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |         return count | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 14:00:16 +01:00
										 |  |  | const connectionString = "postgresql://user:password@localhost:5444/osm-poi" | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  | const tcs = new OsmPoiDatabase(connectionString) | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  | const withCache = new CachedSqlCount(tcs, 60 * 60 * 24) | 
					
						
							|  |  |  | new Server(2345, { ignorePathPrefix: ["summary"] }, [ | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-02-19 13:57:08 +01:00
										 |  |  |         mustMatch: "status.json", | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |         mimetype: "application/json", | 
					
						
							|  |  |  |         handle: async (path: string) => { | 
					
						
							|  |  |  |             const layers = await tcs.getLayers() | 
					
						
							|  |  |  |             const meta = await tcs.getMeta() | 
					
						
							| 
									
										
										
										
											2024-02-18 17:41:38 +01:00
										 |  |  |             return JSON.stringify({ meta, layers: Array.from(layers) }) | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |         }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2024-02-19 13:57:08 +01:00
										 |  |  |         mustMatch: /[a-zA-Z0-9+_-]+\/[0-9]+\/[0-9]+\/[0-9]+\.json/, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |         mimetype: "application/json", // "application/vnd.geo+json",
 | 
					
						
							|  |  |  |         async handle(path) { | 
					
						
							|  |  |  |             const [layers, z, x, y] = path.split(".")[0].split("/") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let sum = 0 | 
					
						
							|  |  |  |             let properties: Record<string, number> = {} | 
					
						
							| 
									
										
										
										
											2024-02-18 15:59:28 +01:00
										 |  |  |             const availableLayers = await tcs.getLayers() | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |             let latSum = 0 | 
					
						
							|  |  |  |             let lonSum = 0 | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |             for (const layer of layers.split("+")) { | 
					
						
							| 
									
										
										
										
											2024-02-18 15:59:28 +01:00
										 |  |  |                 if (!availableLayers.has(layer)) { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |                 const count = await withCache.getCount( | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |                     layer, | 
					
						
							| 
									
										
										
										
											2024-02-19 14:22:16 +01:00
										 |  |  |                     Tiles.tile_index(Number(z), Number(x), Number(y)) | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 properties[layer] = count.count | 
					
						
							| 
									
										
										
										
											2024-02-20 02:17:30 +01:00
										 |  |  |                 if (count.count !== 0) { | 
					
						
							|  |  |  |                     latSum += count.lat * count.count | 
					
						
							|  |  |  |                     lonSum += count.lon * count.count | 
					
						
							|  |  |  |                     sum += count.count | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 15:06:54 +01:00
										 |  |  |             properties["lon"] = lonSum / sum | 
					
						
							|  |  |  |             properties["lat"] = latSum / sum | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 17:39:59 +01:00
										 |  |  |             return JSON.stringify({ ...properties, total: sum }) | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  | ]) | 
					
						
							|  |  |  | console.log( | 
					
						
							|  |  |  |     ">>>", | 
					
						
							|  |  |  |     await tcs.getCount("drinking_water", [ | 
					
						
							|  |  |  |         [3.194358020772171, 51.228073636083394], | 
					
						
							|  |  |  |         [3.2839964396059145, 51.172701162680994], | 
					
						
							|  |  |  |     ]) | 
					
						
							|  |  |  | ) |