aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dumbyUtils.mjs50
-rw-r--r--src/dumbymap.mjs78
-rw-r--r--src/editor.mjs2
-rw-r--r--src/utils.mjs44
4 files changed, 121 insertions, 53 deletions
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs
index 8bd4423..7b06840 100644
--- a/src/dumbyUtils.mjs
+++ b/src/dumbyUtils.mjs
@@ -1,5 +1,6 @@
1import LeaderLine from 'leader-line' 1import LeaderLine from 'leader-line'
2import { insideWindow, insideParent } from './utils' 2import { insideWindow, insideParent } from './utils'
3import proj4 from 'proj4'
3 4
4export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/ 5export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/
5 6
@@ -333,3 +334,52 @@ export const addAnchorByPoint = ({
333 334
334 return { ref: anchorName, link, title: desc } 335 return { ref: anchorName, link, title: desc }
335} 336}
337
338/**
339 * setGeoSchemeByCRS.
340 * @description Add more information into Anchor Element within Geo Scheme by CRS
341 * @param {String} crs - EPSG/ESRI Code for CRS
342 * @return {Function} - Function for link
343 */
344export const setGeoSchemeByCRS = (crs) => (link) => {
345 const transform = proj4(crs, 'EPSG:4326')
346 const params = new URLSearchParams(link.search)
347 let xy = params.get('xy')?.split(',')?.map(Number)
348
349 // Set coords for Geo Scheme
350 if (link.href.startsWith('geo:0,0')) {
351 if (!xy) return null
352
353 const [lon, lat] = transform.forward(xy)
354 .map(value => parseFloat(value.toFixed(6)))
355 link.href = `geo:${lat},${lon}`
356 }
357
358 const [lat, lon] = link.href
359 .match(coordPattern)
360 .slice(1)
361 .map(Number)
362
363 if (!xy) {
364 xy = transform.inverse([lon, lat])
365 params.set('xy', xy)
366 }
367
368 // set query strings
369 params.set('crs', crs)
370 params.set('q', `${lat},${lon}`)
371 link.search = params
372
373 const unit = proj4(crs).oProj.units
374 const invalidDegree = unit === 'degrees' && (
375 (lon > 180 || lon < -180 || lat > 90 || lat < -90) ||
376 (xy.every(v => v.toString().length < 3))
377 )
378 const invalidMeter = unit === 'm' && xy.find(v => v < 100)
379 if (invalidDegree || invalidMeter) {
380 link.replaceWith(document.createTextNode(link.textContent))
381 return null
382 }
383
384 return link
385}
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index 81a65ca..4329bbf 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -4,7 +4,7 @@ import MarkdownItFootnote from 'markdown-it-footnote'
4import MarkdownItFrontMatter from 'markdown-it-front-matter' 4import MarkdownItFrontMatter from 'markdown-it-front-matter'
5import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers' 5import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers'
6import { renderWith, defaultAliases, parseConfigsFromYaml } from 'mapclay' 6import { renderWith, defaultAliases, parseConfigsFromYaml } from 'mapclay'
7import { onRemove, animateRectTransition, throttle, shiftByWindow } from './utils' 7import { onRemove, animateRectTransition, throttle, shiftByWindow, replaceTextNodes } from './utils'
8import { Layout, SideBySide, Overlay } from './Layout' 8import { 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'
@@ -56,27 +56,6 @@ export const markdown2HTML = (container, mdContent) => {
56 .use(MarkdownItFrontMatter) 56 .use(MarkdownItFrontMatter)
57 .use(MarkdownItInjectLinenumbers) 57 .use(MarkdownItInjectLinenumbers)
58 58
59 /** Set up linkify for GeoLinks */
60 const coordinateRegex = /^(\D*)(-?\d+\.?\d*)\s*([,\x2F\uFF0C])\s*(-?\d+\.?\d*)/
61 const coordinateValue = {
62 validate: coordinateRegex,
63 normalize: function (match) {
64 const [, , x, sep, y] = match.text.match(coordinateRegex)
65 match.url = `geo:${y},${x}`
66 match.text = `${x}${sep}${y}`
67 match.index += match.text.indexOf(x) + 1
68 return match
69 },
70 }
71 const patterns = ['[', '(', '📍', '\uFF08', '@', 'twd']
72 patterns.forEach(prefix =>
73 md.linkify.add(prefix, coordinateValue),
74 )
75 md.linkify.add('geo:', {
76 validate: /(-?\d+\.?\d*),(-?\d+\.?\d*)/,
77 normalize: match => match,
78 })
79
80 /** Custom rule for Blocks in DumbyMap */ 59 /** Custom rule for Blocks in DumbyMap */
81 // FIXME A better way to generate blocks 60 // FIXME A better way to generate blocks
82 md.renderer.rules.dumby_block_open = () => '<div>' 61 md.renderer.rules.dumby_block_open = () => '<div>'
@@ -130,7 +109,7 @@ export const generateMaps = (container, {
130 109
131 const htmlHolder = container.querySelector('.SemanticHtml') ?? container 110 const htmlHolder = container.querySelector('.SemanticHtml') ?? container
132 const blocks = addBlocks(htmlHolder) 111 const blocks = addBlocks(htmlHolder)
133 blocks.forEach(b => b.dataset.total = blocks.length) 112 blocks.forEach(b => { b.dataset.total = blocks.length })
134 113
135 const showcase = document.createElement('div') 114 const showcase = document.createElement('div')
136 container.appendChild(showcase) 115 container.appendChild(showcase)
@@ -189,37 +168,32 @@ export const generateMaps = (container, {
189 }) 168 })
190 169
191 /** LINK: Set CRS and GeoLinks */ 170 /** LINK: Set CRS and GeoLinks */
192 register(proj4) 171 const setCRS = new Promise(resolve => {
193 fromEPSGCode(crs).then(projection => { 172 register(proj4)
194 const transform = proj4(crs, 'EPSG:4326').forward 173 fromEPSGCode(crs).then(() => resolve())
174 })
175 const addGeoSchemeByText = new Promise(resolve => {
176 const coordPatterns = [
177 /[\x28\x5B\uFF08]\D*(-?\d+\.?\d*)([\x2F\s])(-?\d+\.?\d*)\D*[\x29\x5D\uFF09]/,
178 /(-?\d+\.?\d*)([,\uFF0C]\s?)(-?\d+\.?\d*)/,
179 ]
180 const re = new RegExp(coordPatterns.map(p => p.source).join('|'), 'g')
181 htmlHolder.querySelectorAll('p')
182 .forEach(p => {
183 replaceTextNodes(p, re, match => {
184 const a = document.createElement('a')
185 a.href = `geo:0,0?xy=${match.at(1)},${match.at(3)}`
186 a.textContent = match.at(0)
187 return a
188 })
189 })
190 resolve()
191 })
195 192
193 Promise.all([setCRS, addGeoSchemeByText]).then(() => {
196 Array.from(container.querySelectorAll(geoLinkSelector)) 194 Array.from(container.querySelectorAll(geoLinkSelector))
197 .map(link => { 195 .map(utils.setGeoSchemeByCRS(crs))
198 // set coordinate as lat/lon in WGS84 196 .filter(link => link instanceof window.HTMLAnchorElement)
199 const params = new URLSearchParams(link.search)
200 const [y, x] = link.href
201 .match(utils.coordPattern)
202 .splice(1)
203 .map(Number)
204 const [lon, lat] = transform([x, y])
205 .map(value => parseFloat(value.toFixed(6)))
206 link.href = `geo:${lat},${lon}`
207
208 // set query strings
209 params.set('xy', `${x},${y}`)
210 params.set('crs', crs)
211 params.set('q', `${lat},${lon}`)
212 link.search = params
213
214 if (projection.getUnits() === 'degrees' &&
215 (lon > 180 || lon < -180 || lat > 90 || lat < -90)
216 ) {
217 link.dataset.valid = false
218 link.title = `Invalid Coordinate, maybe try another crs other than ${crs}`
219 }
220
221 return link
222 })
223 .forEach(utils.createGeoLink) 197 .forEach(utils.createGeoLink)
224 }) 198 })
225 199
diff --git a/src/editor.mjs b/src/editor.mjs
index df6e3bf..d13be58 100644
--- a/src/editor.mjs
+++ b/src/editor.mjs
@@ -2,7 +2,7 @@
2import { markdown2HTML, generateMaps } from './dumbymap' 2import { markdown2HTML, generateMaps } from './dumbymap'
3import { defaultAliases, parseConfigsFromYaml } from 'mapclay' 3import { defaultAliases, parseConfigsFromYaml } from 'mapclay'
4import * as menuItem from './MenuItem' 4import * as menuItem from './MenuItem'
5import { addAnchorByPoint, createGeoLink } from './dumbyUtils.mjs' 5import { addAnchorByPoint } from './dumbyUtils.mjs'
6import { shiftByWindow } from './utils.mjs' 6import { shiftByWindow } from './utils.mjs'
7import * as tutorial from './tutorial' 7import * as tutorial from './tutorial'
8 8
diff --git a/src/utils.mjs b/src/utils.mjs
index d408b3d..1fe3ce5 100644
--- a/src/utils.mjs
+++ b/src/utils.mjs
@@ -133,3 +133,47 @@ export const insideParent = (childElement, parentElement) => {
133 childRect.bottom > parentRect.top + offset 133 childRect.bottom > parentRect.top + offset
134 ) 134 )
135} 135}
136
137/**
138 * replaceTextNodes.
139 * @description Search current nodes by pattern, and replace them by new node
140 * @todo refactor to smaller methods
141 * @param {HTMLElement} element
142 * @param {RegExp} pattern
143 * @param {Function} newNode - Create new node by each result of String.prototype.matchAll
144 */
145export const replaceTextNodes = (
146 element,
147 pattern,
148 newNode = (match) => {
149 const link = document.createElement('a')
150 link.textContent(match.at(0))
151 return link
152 },
153) => {
154 const nodeIterator = document.createNodeIterator(
155 element,
156 window.NodeFilter.SHOW_TEXT,
157 node => node.textContent.match(pattern)
158 ? window.NodeFilter.FILTER_ACCEPT
159 : window.NodeFilter.FILTER_REJECT,
160 )
161
162 let node = nodeIterator.nextNode()
163 while (node) {
164 let index = 0
165 for (const match of node.textContent.matchAll(pattern)) {
166 const text = node.textContent.slice(index, match.index)
167 index = match.index + match.at(0).length
168 node.parentElement.insertBefore(document.createTextNode(text), node)
169 node.parentElement.insertBefore(newNode(match), node)
170 }
171 if (index < node.textContent.length) {
172 const text = node.textContent.slice(index)
173 node.parentElement.insertBefore(document.createTextNode(text), node)
174 }
175
176 node.parentElement.removeChild(node)
177 node = nodeIterator.nextNode()
178 }
179}