diff options
| author | Hsieh Chin Fan <pham@topo.tw> | 2024-11-03 13:58:38 +0800 |
|---|---|---|
| committer | Hsieh Chin Fan <pham@topo.tw> | 2024-11-03 14:36:15 +0800 |
| commit | f4910b66e74034db14619ee384dc261423dc19fc (patch) | |
| tree | 78382b3bb600b61a43b48d417cfbe3873d8da623 | |
| parent | 3086cc484857dea32a33f829ec173adaf0cf8fbe (diff) | |
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
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/Link.mjs | 2 | ||||
| -rw-r--r-- | src/MenuItem.mjs | 65 | ||||
| -rw-r--r-- | 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 @@ | |||
| 44 | "cssprefix": "^2.0.17", | 44 | "cssprefix": "^2.0.17", |
| 45 | "easymde": "^2.18.0", | 45 | "easymde": "^2.18.0", |
| 46 | "leader-line": "^1.0.7", | 46 | "leader-line": "^1.0.7", |
| 47 | "mapclay": "^0.9.0", | 47 | "mapclay": "^0.9.2", |
| 48 | "markdown-it": "^14.1.0", | 48 | "markdown-it": "^14.1.0", |
| 49 | "markdown-it-anchor": "^9.2.0", | 49 | "markdown-it-anchor": "^9.2.0", |
| 50 | "markdown-it-footnote": "^4.0.0", | 50 | "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 => { | |||
| 227 | */ | 227 | */ |
| 228 | export const updateMapCameraByMarker = lonLat => marker => { | 228 | export const updateMapCameraByMarker = lonLat => marker => { |
| 229 | const renderer = marker.closest('.mapclay')?.renderer | 229 | const renderer = marker.closest('.mapclay')?.renderer |
| 230 | renderer.updateCamera({ center: lonLat }, true) | 230 | renderer?.updateCamera?.({ center: lonLat, animation: true }) |
| 231 | } | 231 | } |
| 232 | 232 | ||
| 233 | /** | 233 | /** |
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 @@ | |||
| 1 | import { shiftByWindow } from './utils.mjs' | 1 | import { onRemove, shiftByWindow } from './utils.mjs' |
| 2 | import { addMarkerByPoint } from './dumbyUtils.mjs' | 2 | import { addMarkerByPoint } from './dumbyUtils.mjs' |
| 3 | /* eslint-disable-next-line no-unused-vars */ | 3 | /* eslint-disable-next-line no-unused-vars */ |
| 4 | import { GeoLink, getMarkersFromMaps, getMarkersByGeoLink, removeLeaderLines, updateMapCameraByMarker } from './Link.mjs' | 4 | import { GeoLink, getMarkersFromMaps, getMarkersByGeoLink, removeLeaderLines } from './Link.mjs' |
| 5 | import * as markers from './marker.mjs' | 5 | import * as markers from './marker.mjs' |
| 6 | import { parseConfigsFromYaml } from 'mapclay' | 6 | import { parseConfigsFromYaml } from 'mapclay' |
| 7 | 7 | ||
| @@ -606,8 +606,8 @@ export const addLinkbyGeocoding = (range) => { | |||
| 606 | /** Add spinning circle for Network */ | 606 | /** Add spinning circle for Network */ |
| 607 | e.target.classList.add('with-spinning-circle') | 607 | e.target.classList.add('with-spinning-circle') |
| 608 | const menu = e.target.closest('.dumby-menu') | 608 | const menu = e.target.closest('.dumby-menu') |
| 609 | |||
| 610 | if (!menu) return | 609 | if (!menu) return |
| 610 | |||
| 611 | /** Geocoding by Nominatim */ | 611 | /** Geocoding by Nominatim */ |
| 612 | // TODO Add more params like limit: | 612 | // TODO Add more params like limit: |
| 613 | // https://nominatim.org/release-docs/latest/api/Search/ | 613 | // https://nominatim.org/release-docs/latest/api/Search/ |
| @@ -623,36 +623,71 @@ export const addLinkbyGeocoding = (range) => { | |||
| 623 | } | 623 | } |
| 624 | 624 | ||
| 625 | // Add items for each results | 625 | // Add items for each results |
| 626 | const items = places.map(geocodingResult((a) => { | 626 | const bbox = places.map(p => p.boundingbox).reduce((acc, cur) => [ |
| 627 | Math.min(acc[0], cur[0]), | ||
| 628 | Math.max(acc[1], cur[1]), | ||
| 629 | Math.min(acc[2], cur[2]), | ||
| 630 | Math.max(acc[3], cur[3]), | ||
| 631 | ]) | ||
| 632 | const bounds = [[bbox[2], bbox[0]], [bbox[3], bbox[1]]] | ||
| 633 | const items = places.map(geocodingResult(bounds, (a) => { | ||
| 627 | a.className = 'not-geolink from-geocoding' | 634 | a.className = 'not-geolink from-geocoding' |
| 628 | a.textContent = query | 635 | a.textContent = query |
| 629 | range.deleteContents() | 636 | range.deleteContents() |
| 630 | range.insertNode(a) | 637 | range.insertNode(a) |
| 631 | })) | 638 | })) |
| 632 | menu.replaceChildren(...items) | 639 | menu.replaceChildren(...items) |
| 640 | shiftByWindow(menu) | ||
| 633 | }, | 641 | }, |
| 634 | }) | 642 | }) |
| 635 | } | 643 | } |
| 636 | 644 | ||
| 637 | export const geocodingResult = (callback) => (result) => { | 645 | /** |
| 646 | * geocodingResult. | ||
| 647 | * | ||
| 648 | * @param {Array<Number[]>} bounds - boundingbox in format: [minLon, minLat, maxLon, maxLat] | ||
| 649 | * @param {Function} callback | ||
| 650 | */ | ||
| 651 | export const geocodingResult = (bounds, callback) => (result) => { | ||
| 638 | const item = Item({ | 652 | const item = Item({ |
| 639 | text: result.display_name, | 653 | text: result.display_name, |
| 640 | onclick: () => { | 654 | onclick: (e) => { |
| 655 | e.target.classList.add('clicked') | ||
| 656 | |||
| 641 | const a = document.createElement('a') | 657 | const a = document.createElement('a') |
| 642 | a.href = `geo:${result.lat},${result.lon}?name=${result.name}&osm=${result.osm_type}/${result.osm_id}` | 658 | a.href = `geo:${result.lat},${result.lon}` + |
| 659 | `?name=${result.name}` + | ||
| 660 | `&osm=${result.osm_type}/${result.osm_id}` | ||
| 643 | a.title = result.display_name | 661 | a.title = result.display_name |
| 644 | callback(a) | 662 | callback(a) |
| 645 | }, | 663 | }, |
| 646 | }) | 664 | }) |
| 665 | |||
| 666 | const xy = [result.lon, result.lat] | ||
| 667 | |||
| 668 | const markers = getMarkersFromMaps(xy, { | ||
| 669 | type: 'circle', | ||
| 670 | title: result.display_name, | ||
| 671 | }) | ||
| 672 | const bbox = result.boundingbox | ||
| 673 | const resultBounds = [[bbox[2], bbox[0]], [bbox[3], bbox[1]]] | ||
| 674 | |||
| 647 | item.onmouseover = () => { | 675 | item.onmouseover = () => { |
| 648 | const markers = getMarkersFromMaps( | 676 | markers.forEach(async marker => { |
| 649 | [result.lon, result.lat], | 677 | const renderer = marker.closest('.mapclay')?.renderer |
| 650 | { type: 'circle', title: result.display_name }, | 678 | await renderer.updateCamera({ bounds, duration: 1000, animation: true, padding: 20 }) |
| 651 | ) | 679 | await renderer.updateCamera({ center: xy, duration: 600, animation: true }) |
| 652 | markers.forEach(updateMapCameraByMarker([result.lon, result.lat])) | 680 | await renderer.updateCamera({ bounds: resultBounds, duration: 1500, animation: true, padding: 20 }) |
| 653 | item.onmouseout = () => { | 681 | }) |
| 654 | markers.forEach(m => m.remove()) | ||
| 655 | } | ||
| 656 | } | 682 | } |
| 683 | |||
| 684 | setTimeout(() => { | ||
| 685 | onRemove(item.closest('.menu'), () => { | ||
| 686 | if (item.classList.contains('clicked')) return | ||
| 687 | markers.forEach(marker => marker.remove()) | ||
| 688 | }), | ||
| 689 | 100 | ||
| 690 | }) | ||
| 691 | |||
| 657 | return item | 692 | return item |
| 658 | } | 693 | } |
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) { | |||
| 113 | * @param {HTMLElement} element | 113 | * @param {HTMLElement} element |
| 114 | */ | 114 | */ |
| 115 | export const shiftByWindow = element => { | 115 | export const shiftByWindow = element => { |
| 116 | delete element.style.transform | ||
| 116 | const rect = element.getBoundingClientRect() | 117 | const rect = element.getBoundingClientRect() |
| 117 | const offsetX = window.innerWidth - rect.left - rect.width | 118 | const offsetX = window.innerWidth - rect.left - rect.width |
| 118 | const offsetY = window.innerHeight - rect.top - rect.height | 119 | const offsetY = window.innerHeight - rect.top - rect.height |