Offline-capable geomap, meant for storing location bookmarks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: cleanup bookmarks

+69 -9
+6 -2
www/models/adapters/geojson.ts
··· 44 44 } 45 45 46 46 const description = (raw.description ?? raw.desc) as string | undefined 47 - const googleMapsUrl = (raw.googleMapsUrl ?? raw.google_maps_url) as 47 + 48 + // Collect any link-like fields into the links map 49 + const links: Record<string, string> = {} 50 + const gmapsUrl = (raw.googleMapsUrl ?? raw.google_maps_url) as 48 51 | string 49 52 | undefined 53 + if (gmapsUrl?.trim()) links['Google Maps'] = gmapsUrl.trim() 50 54 51 55 // Nominatim-style fields that may appear in exported GeoJSON 52 56 const nominatimId = typeof raw.nominatimId === 'number' ··· 68 72 name: name?.trim() || undefined, 69 73 displayName: displayName?.trim() || undefined, 70 74 description: description?.trim() || undefined, 71 - googleMapsUrl: googleMapsUrl?.trim() || undefined, 75 + links: Object.keys(links).length > 0 ? links : undefined, 72 76 nominatimId, 73 77 nominatimCategory, 74 78 nominatimType,
+1 -1
www/models/schema/v0.ts
··· 39 39 addressType: z.optional(z.string()), 40 40 displayName: z.optional(z.string()), 41 41 description: z.optional(z.string()), 42 - googleMapsUrl: z.optional(z.string()), 42 + links: z.optional(z.record(z.string(), z.string())), 43 43 importance: z.optional(z.number()), 44 44 name: z.optional(z.string()), 45 45 nominatimCategory: z.optional(z.string()),
+37
www/routes/bookmarks.ts
··· 31 31 super.connectedCallback() 32 32 this.#sync() 33 33 app.addEventListener(this.#onUpdate) 34 + this.#checkEditHash() 35 + } 36 + 37 + #checkEditHash() { 38 + const match = location.hash.match(/[?&]edit=([^&]+)/) 39 + if (!match) return 40 + const id = decodeURIComponent(match[1]) 41 + const bookmark = this.bookmarks.find((b) => b.id === id) 42 + if (bookmark) { 43 + this.editingBookmark = bookmark 44 + this.requestUpdate() 45 + } 46 + history.replaceState( 47 + null, 48 + '', 49 + location.href.replace(location.hash, '#!/bookmarks'), 50 + ) 34 51 } 35 52 36 53 override disconnectedCallback() { ··· 569 586 .value="${this.editingBookmark.properties.description ?? 570 587 ''}" 571 588 ></textarea> 589 + ${(() => { 590 + const links = this.editingBookmark.properties.links 591 + const entries = links ? Object.entries(links) : [] 592 + return entries.length > 0 593 + ? html` 594 + <details> 595 + <summary>External Links</summary> 596 + ${entries.map(([label, url]) => 597 + html` 598 + <a 599 + href="${url}" 600 + target="_blank" 601 + rel="noopener noreferrer" 602 + >${label}</a> 603 + ` 604 + )} 605 + </details> 606 + ` 607 + : '' 608 + })()} 572 609 <p class="bm-dialog-coords"> 573 610 ${this.editingBookmark.geometry.coordinates[1].toFixed( 574 611 5,
+24 -5
www/routes/map.ts
··· 262 262 ...b, 263 263 properties: { 264 264 ...b.properties, 265 + _id: b.id, 265 266 _displayName: bookmarkDisplayName(b), 266 267 _color: b.categories[0] 267 268 ? (collectionColors.get(b.categories[0]) ?? null) ··· 291 292 this.#map.on('click', 'bookmarks', (e) => { 292 293 if (!e.features?.length || !this.#map) return 293 294 const feature = e.features[0] 294 - const coords = (feature.geometry as { coordinates: [number, number] }) 295 - .coordinates 295 + const coords = (feature.geometry as unknown as { 296 + coordinates: [number, number] 297 + }).coordinates 296 298 const name = feature.properties?._displayName as string 299 + const id = feature.properties?._id as string | undefined 297 300 this.#bookmarkPopup?.remove() 301 + const container = document.createElement('div') 302 + container.style.cssText = 'display:flex;flex-direction:column;gap:4px' 303 + const nameEl = document.createElement('strong') 304 + nameEl.textContent = name 305 + container.append(nameEl) 306 + if (id) { 307 + const editLink = document.createElement('a') 308 + editLink.href = `#!/bookmarks?edit=${encodeURIComponent(id)}` 309 + editLink.textContent = 'Edit bookmark' 310 + editLink.style.cssText = 'font-size:0.8em' 311 + container.append(editLink) 312 + } 298 313 this.#bookmarkPopup = new maplibregl.Popup({ offset: 10 }) 299 314 .setLngLat(coords) 300 - .setHTML(`<strong>${name}</strong>`) 315 + .setDOMContent(container) 301 316 .addTo(this.#map) 302 317 }) 303 318 this.#map.on('mouseenter', 'bookmarks', () => { ··· 338 353 url: 'pmtiles://world.pmtiles', 339 354 attribution: 'Natural Earth', 340 355 }) 341 - worldLayers.forEach((layer) => this.#map!.addLayer(layer)) 356 + worldLayers.forEach((layer) => 357 + this.#map!.addLayer(layer as maplibregl.AddLayerObject) 358 + ) 342 359 } 343 360 } 344 361 ··· 356 373 type: 'vector', 357 374 url: `pmtiles://${tile.filename}`, 358 375 }) 359 - layers(tile.name).forEach((layer) => this.#map!.addLayer(layer)) 376 + layers(tile.name).forEach((layer) => 377 + this.#map!.addLayer(layer as maplibregl.AddLayerObject) 378 + ) 360 379 } 361 380 } 362 381 }
+1 -1
www/utils/layers.ts
··· 1 1 // https://blog.wxm.be/2023/11/25/osm-to-pmtiles-with-tilemaker.html 2 2 // https://github.com/protomaps/basemaps/issues/15#issuecomment-1436048491 3 3 // https://github.com/openmaptiles/maptiler-basic-gl-style 4 - export default function (sourceName) { 4 + export default function (sourceName: string) { 5 5 return [ 6 6 { 7 7 'id': `${sourceName}-landuse-residential`,