aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-11 15:01:40 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-11 15:01:40 +0800
commit184b3004d82806f1b31c94701e75585fb8f2721b (patch)
tree2503280b32684fad893c00115d8eea45af4e7731
parenta7d62c58032915e23bfda2eb36b2dcf0fc5ae4ba (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
-rw-r--r--package.json4
-rw-r--r--src/dumbyUtils.mjs35
-rw-r--r--src/dumbymap.mjs42
3 files changed, 62 insertions, 19 deletions
diff --git a/package.json b/package.json
index 59f4800..a6c7027 100644
--- a/package.json
+++ b/package.json
@@ -54,8 +54,10 @@
54 "markdown-it-front-matter": "^0.2.4", 54 "markdown-it-front-matter": "^0.2.4",
55 "markdown-it-inject-linenumbers": "^0.3.0", 55 "markdown-it-inject-linenumbers": "^0.3.0",
56 "markdown-it-toc-done-right": "^4.2.0", 56 "markdown-it-toc-done-right": "^4.2.0",
57 "ol": "^10.2.1",
57 "plain-draggable": "^2.5.15", 58 "plain-draggable": "^2.5.15",
58 "plain-modal": "^1.0.34" 59 "plain-modal": "^1.0.34",
60 "proj4": "^2.12.1"
59 }, 61 },
60 "author": "Hsiehg Chin Fan <pham@topo.tw>", 62 "author": "Hsiehg Chin Fan <pham@topo.tw>",
61 "homepage": "https://outdoorsafetylab.github.io/dumbymap", 63 "homepage": "https://outdoorsafetylab.github.io/dumbymap",
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 @@
1import LeaderLine from 'leader-line' 1import LeaderLine from 'leader-line'
2import { insideWindow, insideParent } from './utils' 2import { insideWindow, insideParent } from './utils'
3 3
4export 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 */
139export const createGeoLink = (link) => { 142export 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 */
232const updateMapCameraByMarker = xy => marker => { 243const 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'
9import * as utils from './dumbyUtils' 9import * as utils from './dumbyUtils'
10import * as menuItem from './MenuItem' 10import * as menuItem from './MenuItem'
11import PlainModal from 'plain-modal' 11import PlainModal from 'plain-modal'
12import proj4 from 'proj4'
13import { register, fromEPSGCode } from 'ol/proj/proj4'
12 14
13/** Selector of special HTML Elements */ 15/** Selector of special HTML Elements */
14const mapBlockSelector = 'pre:has(.language-map)' 16const 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