From f4910b66e74034db14619ee384dc261423dc19fc Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Sun, 3 Nov 2024 13:58:38 +0800 Subject: feat: use bbox to update camera for result of Geocoding * show markers from all Geocoding Results * update mapclay for latest updateCamera method * 3 steps for camera animation: 1. zoom out to bbox which contains all Geocoding Result 2. pan to marker of hovered result 3. zoom in to bbox from nonimatim query * remove markers except selected Geocoding Result --- package.json | 2 +- src/Link.mjs | 2 +- src/MenuItem.mjs | 65 +++++++++++++++++++++++++++++++++++++++++++------------- src/utils.mjs | 1 + 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index a08a286..64ed496 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "cssprefix": "^2.0.17", "easymde": "^2.18.0", "leader-line": "^1.0.7", - "mapclay": "^0.9.0", + "mapclay": "^0.9.2", "markdown-it": "^14.1.0", "markdown-it-anchor": "^9.2.0", "markdown-it-footnote": "^4.0.0", diff --git a/src/Link.mjs b/src/Link.mjs index 7678f3a..685de3a 100644 --- a/src/Link.mjs +++ b/src/Link.mjs @@ -227,7 +227,7 @@ const isAnchorVisible = anchor => { */ export const updateMapCameraByMarker = lonLat => marker => { const renderer = marker.closest('.mapclay')?.renderer - renderer.updateCamera({ center: lonLat }, true) + renderer?.updateCamera?.({ center: lonLat, animation: true }) } /** diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs index 6c6e6cf..8bee5c1 100644 --- a/src/MenuItem.mjs +++ b/src/MenuItem.mjs @@ -1,7 +1,7 @@ -import { shiftByWindow } from './utils.mjs' +import { onRemove, shiftByWindow } from './utils.mjs' import { addMarkerByPoint } from './dumbyUtils.mjs' /* eslint-disable-next-line no-unused-vars */ -import { GeoLink, getMarkersFromMaps, getMarkersByGeoLink, removeLeaderLines, updateMapCameraByMarker } from './Link.mjs' +import { GeoLink, getMarkersFromMaps, getMarkersByGeoLink, removeLeaderLines } from './Link.mjs' import * as markers from './marker.mjs' import { parseConfigsFromYaml } from 'mapclay' @@ -606,8 +606,8 @@ export const addLinkbyGeocoding = (range) => { /** Add spinning circle for Network */ e.target.classList.add('with-spinning-circle') const menu = e.target.closest('.dumby-menu') - if (!menu) return + /** Geocoding by Nominatim */ // TODO Add more params like limit: // https://nominatim.org/release-docs/latest/api/Search/ @@ -623,36 +623,71 @@ export const addLinkbyGeocoding = (range) => { } // Add items for each results - const items = places.map(geocodingResult((a) => { + const bbox = places.map(p => p.boundingbox).reduce((acc, cur) => [ + Math.min(acc[0], cur[0]), + Math.max(acc[1], cur[1]), + Math.min(acc[2], cur[2]), + Math.max(acc[3], cur[3]), + ]) + const bounds = [[bbox[2], bbox[0]], [bbox[3], bbox[1]]] + const items = places.map(geocodingResult(bounds, (a) => { a.className = 'not-geolink from-geocoding' a.textContent = query range.deleteContents() range.insertNode(a) })) menu.replaceChildren(...items) + shiftByWindow(menu) }, }) } -export const geocodingResult = (callback) => (result) => { +/** + * geocodingResult. + * + * @param {Array} bounds - boundingbox in format: [minLon, minLat, maxLon, maxLat] + * @param {Function} callback + */ +export const geocodingResult = (bounds, callback) => (result) => { const item = Item({ text: result.display_name, - onclick: () => { + onclick: (e) => { + e.target.classList.add('clicked') + const a = document.createElement('a') - a.href = `geo:${result.lat},${result.lon}?name=${result.name}&osm=${result.osm_type}/${result.osm_id}` + a.href = `geo:${result.lat},${result.lon}` + + `?name=${result.name}` + + `&osm=${result.osm_type}/${result.osm_id}` a.title = result.display_name callback(a) }, }) + + const xy = [result.lon, result.lat] + + const markers = getMarkersFromMaps(xy, { + type: 'circle', + title: result.display_name, + }) + const bbox = result.boundingbox + const resultBounds = [[bbox[2], bbox[0]], [bbox[3], bbox[1]]] + item.onmouseover = () => { - const markers = getMarkersFromMaps( - [result.lon, result.lat], - { type: 'circle', title: result.display_name }, - ) - markers.forEach(updateMapCameraByMarker([result.lon, result.lat])) - item.onmouseout = () => { - markers.forEach(m => m.remove()) - } + markers.forEach(async marker => { + const renderer = marker.closest('.mapclay')?.renderer + await renderer.updateCamera({ bounds, duration: 1000, animation: true, padding: 20 }) + await renderer.updateCamera({ center: xy, duration: 600, animation: true }) + await renderer.updateCamera({ bounds: resultBounds, duration: 1500, animation: true, padding: 20 }) + }) } + + setTimeout(() => { + onRemove(item.closest('.menu'), () => { + if (item.classList.contains('clicked')) return + markers.forEach(marker => marker.remove()) + }), + 100 + }) + return item } diff --git a/src/utils.mjs b/src/utils.mjs index c0da23a..4cce323 100644 --- a/src/utils.mjs +++ b/src/utils.mjs @@ -113,6 +113,7 @@ export function debounce (func, delay = 1000) { * @param {HTMLElement} element */ export const shiftByWindow = element => { + delete element.style.transform const rect = element.getBoundingClientRect() const offsetX = window.innerWidth - rect.left - rect.width const offsetY = window.innerHeight - rect.top - rect.height -- cgit v1.2.3-70-g09d2