aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-11 15:18:16 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-11 15:18:16 +0800
commit68ad9917a830475bf5424718642de4c058dc88ca (patch)
tree80ae0c0a3e5b204de5d98090de61ace31979f0c0 /src
parent3ed5047f7ffdc019f80715dbbe8f30712987d942 (diff)
parent2a7a840c25fcdb1653538514fb6609b0dea61b66 (diff)
Merge branch 'crs'
Diffstat (limited to 'src')
-rw-r--r--src/css/dumbymap.css6
-rw-r--r--src/dumbyUtils.mjs39
-rw-r--r--src/dumbymap.mjs59
-rw-r--r--src/editor.mjs13
4 files changed, 94 insertions, 23 deletions
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css
index 52d0ded..7aff339 100644
--- a/src/css/dumbymap.css
+++ b/src/css/dumbymap.css
@@ -152,6 +152,12 @@ a[href^='http']:not(:has(img))::after,
152 background-color: #9ee7ea; 152 background-color: #9ee7ea;
153 } 153 }
154 154
155 &[data-valid="false"] {
156 background-color: lightgray;
157
158 opacity: 0.7;
159 }
160
155 &:hover, &.drag { 161 &:hover, &.drag {
156 background-image: none; 162 background-image: none;
157 163
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs
index 170cb16..2f92c18 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,26 +141,32 @@ 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
159 // LeaderLine 166 // LeaderLine
160 link.onmouseover = () => { 167 link.onmouseover = () => {
168 if (link.dataset.valid === 'false') return
169
161 const anchors = getMarkersFromMaps(link) 170 const anchors = getMarkersFromMaps(link)
162 anchors 171 anchors
163 .filter(isAnchorVisible) 172 .filter(isAnchorVisible)
@@ -169,8 +178,14 @@ export const createGeoLink = (link) => {
169 link.onmouseout = () => removeLeaderLines(link) 178 link.onmouseout = () => removeLeaderLines(link)
170 link.onclick = (event) => { 179 link.onclick = (event) => {
171 event.preventDefault() 180 event.preventDefault()
181 if (link.dataset.valid === 'false') return
182
172 removeLeaderLines(link) 183 removeLeaderLines(link)
173 getMarkersFromMaps(link).forEach(updateMapCameraByMarker(link.xy)) 184 getMarkersFromMaps(link)
185 .forEach(updateMapCameraByMarker([
186 Number(link.dataset.lon),
187 Number(link.dataset.lat),
188 ]))
174 } 189 }
175 return true 190 return true
176} 191}
@@ -229,9 +244,9 @@ const removeLeaderLines = link => {
229 * @param {Number[]} xy 244 * @param {Number[]} xy
230 * @return {Function} function 245 * @return {Function} function
231 */ 246 */
232const updateMapCameraByMarker = xy => marker => { 247const updateMapCameraByMarker = lonLat => marker => {
233 const renderer = marker.closest('.mapclay')?.renderer 248 const renderer = marker.closest('.mapclay')?.renderer
234 renderer.updateCamera({ center: xy }, true) 249 renderer.updateCamera({ center: lonLat }, true)
235} 250}
236 251
237/** 252/**
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index 35a4614..974d4a3 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 },
@@ -111,11 +113,17 @@ export const markdown2HTML = (container, mdContent) => {
111 * 113 *
112 * @param {HTMLElement} container - The container element for the maps 114 * @param {HTMLElement} container - The container element for the maps
113 * @param {Object} options - Configuration options 115 * @param {Object} options - Configuration options
116 * @param {string} options.crs - CRS in EPSG/ESRI code, see epsg.io
114 * @param {number} [options.delay=1000] - Delay before rendering maps (in milliseconds) 117 * @param {number} [options.delay=1000] - Delay before rendering maps (in milliseconds)
115 * @param {Function} options.mapCallback - Callback function to be called after map rendering 118 * @param {Function} options.mapCallback - Callback function to be called after map rendering
116 */ 119 */
117export const generateMaps = (container, { layouts = [], delay, renderCallback } = {}) => { 120export const generateMaps = (container, {
118 /** Prepare Contaner/HTML Holder/Showcase */ 121 crs = 'EPSG:4326',
122 layouts = [],
123 delay,
124 renderCallback,
125} = {}) => {
126 /** Prepare Contaner/HTML-Holder/Showcase */
119 container.classList.add('Dumby') 127 container.classList.add('Dumby')
120 delete container.dataset.layout 128 delete container.dataset.layout
121 container.dataset.layout = defaultLayouts[0].name 129 container.dataset.layout = defaultLayouts[0].name
@@ -159,15 +167,50 @@ export const generateMaps = (container, { layouts = [], delay, renderCallback }
159 switchToNextLayout: throttle(utils.switchToNextLayout, 300), 167 switchToNextLayout: throttle(utils.switchToNextLayout, 300),
160 }, 168 },
161 } 169 }
162 Object.entries(dumbymap.utils).forEach(([util, func]) => { 170 Object.entries(dumbymap.utils).forEach(([util, value]) => {
163 dumbymap.utils[util] = func.bind(dumbymap) 171 if (typeof value === 'function') {
172 dumbymap.utils[util] = value.bind(dumbymap)
173 }
164 }) 174 })
165 175
166 /** Create GeoLinks and DocLinks */ 176 /** Create GeoLinks and DocLinks */
167 container.querySelectorAll(docLinkSelector) 177 container.querySelectorAll(docLinkSelector)
168 .forEach(utils.createDocLink) 178 .forEach(utils.createDocLink)
169 container.querySelectorAll(geoLinkSelector) 179
170 .forEach(utils.createGeoLink) 180 /** Set CRS and GeoLinks */
181 register(proj4)
182 fromEPSGCode(crs).then(projection => {
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 if (projection.getUnits() === 'degrees' &&
204 (lon > 180 || lon < -180 || lat > 90 || lat < -90)
205 ) {
206 link.dataset.valid = false
207 link.title = `Invalid Coordinate, maybe try another crs other than ${crs}`
208 }
209
210 return link
211 })
212 .forEach(utils.createGeoLink)
213 })
171 214
172 /** 215 /**
173 * mapFocusObserver. observe for map focus 216 * mapFocusObserver. observe for map focus
diff --git a/src/editor.mjs b/src/editor.mjs
index 04c0502..e12073f 100644
--- a/src/editor.mjs
+++ b/src/editor.mjs
@@ -14,15 +14,20 @@ import LeaderLine from 'leader-line'
14 */ 14 */
15 15
16// Set up Containers {{{ 16// Set up Containers {{{
17/** Variables about dumbymap and editor **/ 17
18/** Variables: page */
18const url = new URL(window.location) 19const url = new URL(window.location)
20const pageParams = url.searchParams
21const crs = pageParams.get('crs') ?? 'EPSG:4326'
22
23/** Variables: dumbymap and editor **/
19const context = document.querySelector('[data-mode]') 24const context = document.querySelector('[data-mode]')
20const dumbyContainer = document.querySelector('.DumbyMap') 25const dumbyContainer = document.querySelector('.DumbyMap')
21dumbyContainer.dataset.scrollLine = '' 26dumbyContainer.dataset.scrollLine = ''
22const textArea = document.querySelector('.editor textarea') 27const textArea = document.querySelector('.editor textarea')
23let dumbymap 28let dumbymap
24 29
25/** Variables about Reference Style Links in Markdown */ 30/** Variables: Reference Style Links in Markdown */
26const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(\S+)(\s["'](\S+)["'])?/ 31const refLinkPattern = /\[([^\x5B\x5D]+)\]:\s+(\S+)(\s["'](\S+)["'])?/
27let refLinks = [] 32let refLinks = []
28 33
@@ -516,7 +521,9 @@ const menuForEditor = (event, menu) => {
516const updateDumbyMap = (callback = null) => { 521const updateDumbyMap = (callback = null) => {
517 markdown2HTML(dumbyContainer, editor.value()) 522 markdown2HTML(dumbyContainer, editor.value())
518 // debounceForMap(dumbyContainer, afterMapRendered) 523 // debounceForMap(dumbyContainer, afterMapRendered)
519 dumbymap = generateMaps(dumbyContainer) 524 dumbymap = generateMaps(dumbyContainer, {
525 crs,
526 })
520 // Set onscroll callback 527 // Set onscroll callback
521 const htmlHolder = dumbymap.htmlHolder 528 const htmlHolder = dumbymap.htmlHolder
522 htmlHolder.onscroll = updateScrollLine(htmlHolder) 529 htmlHolder.onscroll = updateScrollLine(htmlHolder)