diff options
| author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-11 15:01:40 +0800 |
|---|---|---|
| committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-11 15:01:40 +0800 |
| commit | 184b3004d82806f1b31c94701e75585fb8f2721b (patch) | |
| tree | 2503280b32684fad893c00115d8eea45af4e7731 /src | |
| parent | a7d62c58032915e23bfda2eb36b2dcf0fc5ae4ba (diff) | |
feat: implement crs in GeoLink
Now GeoLink has the following format:
geo:<LON>,<LAT>?q=<LON>,<LAT>&xy=<x,y>,crs=<EPSG:CODE>
When calling addMarker or updateCamera, only use [lon,lat] format
Diffstat (limited to 'src')
| -rw-r--r-- | src/dumbyUtils.mjs | 35 | ||||
| -rw-r--r-- | src/dumbymap.mjs | 42 |
2 files changed, 59 insertions, 18 deletions
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index 170cb16..2e71ee6 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs | |||
| @@ -1,6 +1,8 @@ | |||
| 1 | import LeaderLine from 'leader-line' | 1 | import LeaderLine from 'leader-line' |
| 2 | import { insideWindow, insideParent } from './utils' | 2 | import { insideWindow, insideParent } from './utils' |
| 3 | 3 | ||
| 4 | export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/ | ||
| 5 | |||
| 4 | /** | 6 | /** |
| 5 | * focusNextMap. | 7 | * focusNextMap. |
| 6 | * | 8 | * |
| @@ -100,11 +102,12 @@ const getMarkersFromMaps = link => { | |||
| 100 | .filter(map => link.targets ? link.targets.includes(map.id) : true) | 102 | .filter(map => link.targets ? link.targets.includes(map.id) : true) |
| 101 | .map(map => { | 103 | .map(map => { |
| 102 | const renderer = map.renderer | 104 | const renderer = map.renderer |
| 103 | const markerTitle = `${link.targets ?? 'all'}@${link.xy}` | 105 | const markerTitle = `${link.targets ?? 'all'}@${link.dataset.xy}` |
| 106 | const lonLat = [Number(link.dataset.lon), Number(link.dataset.lat)] | ||
| 104 | 107 | ||
| 105 | return map.querySelector(`.marker[title="${markerTitle}"]`) ?? | 108 | return map.querySelector(`.marker[title="${markerTitle}"]`) ?? |
| 106 | renderer.addMarker({ | 109 | renderer.addMarker({ |
| 107 | xy: link.xy, | 110 | xy: lonLat, |
| 108 | title: markerTitle, | 111 | title: markerTitle, |
| 109 | type: link.type, | 112 | type: link.type, |
| 110 | }) | 113 | }) |
| @@ -138,21 +141,25 @@ const addLeaderLine = (link, target) => { | |||
| 138 | */ | 141 | */ |
| 139 | export const createGeoLink = (link) => { | 142 | export const createGeoLink = (link) => { |
| 140 | const url = new URL(link.href) | 143 | const url = new URL(link.href) |
| 141 | const xyInParams = url.searchParams.get('xy')?.split(',')?.map(Number) | 144 | const params = new URLSearchParams(link.search) |
| 142 | const xy = xyInParams ?? url?.href | 145 | const xyInParams = params.get('xy')?.split(',')?.map(Number) |
| 143 | ?.match(/^geo:([-]?[0-9.]+),([-]?[0-9.]+)/) | 146 | const [lon, lat] = url.href |
| 147 | ?.match(coordPattern) | ||
| 144 | ?.splice(1) | 148 | ?.splice(1) |
| 145 | ?.reverse() | 149 | ?.reverse() |
| 146 | ?.map(Number) | 150 | ?.map(Number) |
| 151 | const xy = xyInParams ?? [lon, lat] | ||
| 147 | 152 | ||
| 148 | if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false | 153 | if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false |
| 149 | 154 | ||
| 150 | // Geo information in link | 155 | // Geo information in link |
| 151 | link.url = url | 156 | link.dataset.xy = xy |
| 152 | link.xy = xy | 157 | link.dataset.lon = lon |
| 158 | link.dataset.lat = lat | ||
| 153 | link.classList.add('with-leader-line', 'geolink') | 159 | link.classList.add('with-leader-line', 'geolink') |
| 154 | link.targets = link.url.searchParams.get('id')?.split(',') ?? null | 160 | // TODO refactor as data attribute |
| 155 | link.type = link.url.searchParams.get('type') ?? null | 161 | link.targets = params.get('id')?.split(',') ?? null |
| 162 | link.type = params.get('type') ?? null | ||
| 156 | 163 | ||
| 157 | link.lines = [] | 164 | link.lines = [] |
| 158 | 165 | ||
| @@ -170,7 +177,11 @@ export const createGeoLink = (link) => { | |||
| 170 | link.onclick = (event) => { | 177 | link.onclick = (event) => { |
| 171 | event.preventDefault() | 178 | event.preventDefault() |
| 172 | removeLeaderLines(link) | 179 | removeLeaderLines(link) |
| 173 | getMarkersFromMaps(link).forEach(updateMapCameraByMarker(link.xy)) | 180 | getMarkersFromMaps(link) |
| 181 | .forEach(updateMapCameraByMarker([ | ||
| 182 | Number(link.dataset.lon), | ||
| 183 | Number(link.dataset.lat) | ||
| 184 | ])) | ||
| 174 | } | 185 | } |
| 175 | return true | 186 | return true |
| 176 | } | 187 | } |
| @@ -229,9 +240,9 @@ const removeLeaderLines = link => { | |||
| 229 | * @param {Number[]} xy | 240 | * @param {Number[]} xy |
| 230 | * @return {Function} function | 241 | * @return {Function} function |
| 231 | */ | 242 | */ |
| 232 | const updateMapCameraByMarker = xy => marker => { | 243 | const updateMapCameraByMarker = lonLat => marker => { |
| 233 | const renderer = marker.closest('.mapclay')?.renderer | 244 | const renderer = marker.closest('.mapclay')?.renderer |
| 234 | renderer.updateCamera({ center: xy }, true) | 245 | renderer.updateCamera({ center: lonLat }, true) |
| 235 | } | 246 | } |
| 236 | 247 | ||
| 237 | /** | 248 | /** |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 593875e..6602f29 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
| @@ -9,6 +9,8 @@ import { Layout, SideBySide, Overlay } from './Layout' | |||
| 9 | import * as utils from './dumbyUtils' | 9 | import * as utils from './dumbyUtils' |
| 10 | import * as menuItem from './MenuItem' | 10 | import * as menuItem from './MenuItem' |
| 11 | import PlainModal from 'plain-modal' | 11 | import PlainModal from 'plain-modal' |
| 12 | import proj4 from 'proj4' | ||
| 13 | import { register, fromEPSGCode } from 'ol/proj/proj4' | ||
| 12 | 14 | ||
| 13 | /** Selector of special HTML Elements */ | 15 | /** Selector of special HTML Elements */ |
| 14 | const mapBlockSelector = 'pre:has(.language-map)' | 16 | const mapBlockSelector = 'pre:has(.language-map)' |
| @@ -59,8 +61,8 @@ export const markdown2HTML = (container, mdContent) => { | |||
| 59 | validate: coordinateRegex, | 61 | validate: coordinateRegex, |
| 60 | normalize: function (match) { | 62 | normalize: function (match) { |
| 61 | const [, , x, sep, y] = match.text.match(coordinateRegex) | 63 | const [, , x, sep, y] = match.text.match(coordinateRegex) |
| 62 | match.url = `geo:${y},${x}?xy=${x},${y}` | 64 | match.url = `geo:${y},${x}` |
| 63 | match.text = `${x}${sep} ${y}` | 65 | match.text = `${x}${sep}${y}` |
| 64 | match.index += match.text.indexOf(x) + 1 | 66 | match.index += match.text.indexOf(x) + 1 |
| 65 | return match | 67 | return match |
| 66 | }, | 68 | }, |
| @@ -165,15 +167,43 @@ export const generateMaps = (container, { | |||
| 165 | switchToNextLayout: throttle(utils.switchToNextLayout, 300), | 167 | switchToNextLayout: throttle(utils.switchToNextLayout, 300), |
| 166 | }, | 168 | }, |
| 167 | } | 169 | } |
| 168 | Object.entries(dumbymap.utils).forEach(([util, func]) => { | 170 | Object.entries(dumbymap.utils).forEach(([util, value]) => { |
| 169 | dumbymap.utils[util] = func.bind(dumbymap) | 171 | if (typeof value === 'function') { |
| 172 | dumbymap.utils[util] = value.bind(dumbymap) | ||
| 173 | } | ||
| 170 | }) | 174 | }) |
| 171 | 175 | ||
| 172 | /** Create GeoLinks and DocLinks */ | 176 | /** Create GeoLinks and DocLinks */ |
| 173 | container.querySelectorAll(docLinkSelector) | 177 | container.querySelectorAll(docLinkSelector) |
| 174 | .forEach(utils.createDocLink) | 178 | .forEach(utils.createDocLink) |
| 175 | container.querySelectorAll(geoLinkSelector) | 179 | |
| 176 | .forEach(utils.createGeoLink) | 180 | /** Set CRS and GeoLinks */ |
| 181 | register(proj4) | ||
| 182 | fromEPSGCode(crs).then(() => { | ||
| 183 | const transform = proj4(crs, 'EPSG:4326').forward | ||
| 184 | |||
| 185 | Array.from(container.querySelectorAll(geoLinkSelector)) | ||
| 186 | .map(link => { | ||
| 187 | // set coordinate as lat/lon in WGS84 | ||
| 188 | const params = new URLSearchParams(link.search) | ||
| 189 | const [y, x] = link.href | ||
| 190 | .match(utils.coordPattern) | ||
| 191 | .splice(1) | ||
| 192 | .map(Number) | ||
| 193 | const [lon, lat] = transform([x, y]) | ||
| 194 | .map(value => value.toFixed(6)) | ||
| 195 | link.href = `geo:${lat},${lon}` | ||
| 196 | |||
| 197 | // set query strings | ||
| 198 | params.set('xy', `${x},${y}`) | ||
| 199 | params.set('crs', crs) | ||
| 200 | params.set('q', `${lat},${lon}`) | ||
| 201 | link.search = params | ||
| 202 | |||
| 203 | return link | ||
| 204 | }) | ||
| 205 | .forEach(utils.createGeoLink) | ||
| 206 | }) | ||
| 177 | 207 | ||
| 178 | /** | 208 | /** |
| 179 | * mapFocusObserver. observe for map focus | 209 | * mapFocusObserver. observe for map focus |