umap-project 2.1.3__py3-none-any.whl → 2.2.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (196) hide show
  1. umap/__init__.py +1 -1
  2. umap/context_processors.py +1 -0
  3. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/en/LC_MESSAGES/django.po +32 -32
  5. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  8. umap/migrations/0020_alter_tilelayer_url_template.py +19 -0
  9. umap/migrations/0021_remove_map_description.py +16 -0
  10. umap/models.py +8 -6
  11. umap/settings/base.py +1 -0
  12. umap/static/umap/base.css +29 -151
  13. umap/static/umap/content.css +7 -25
  14. umap/static/umap/css/icon.css +112 -0
  15. umap/static/umap/css/panel.css +140 -0
  16. umap/static/umap/img/16-white.svg +5 -1
  17. umap/static/umap/img/16.svg +7 -4
  18. umap/static/umap/img/24-white.svg +3 -1
  19. umap/static/umap/img/24.svg +3 -4
  20. umap/static/umap/img/source/16-white.svg +176 -940
  21. umap/static/umap/img/source/16.svg +8 -5
  22. umap/static/umap/img/source/24-white.svg +5 -3
  23. umap/static/umap/img/source/24.svg +6 -7
  24. umap/static/umap/js/modules/browser.js +82 -73
  25. umap/static/umap/js/modules/dompurify.js +12 -0
  26. umap/static/umap/js/modules/facets.js +148 -0
  27. umap/static/umap/js/modules/global.js +9 -1
  28. umap/static/umap/js/modules/i18n.js +7 -0
  29. umap/static/umap/js/modules/orderable.js +84 -0
  30. umap/static/umap/js/modules/panel.js +76 -0
  31. umap/static/umap/js/modules/request.js +0 -1
  32. umap/static/umap/js/modules/schema.js +324 -223
  33. umap/static/umap/js/modules/urls.js +1 -16
  34. umap/static/umap/js/modules/utils.js +340 -0
  35. umap/static/umap/js/umap.controls.js +183 -330
  36. umap/static/umap/js/umap.core.js +60 -364
  37. umap/static/umap/js/umap.datalayer.permissions.js +1 -1
  38. umap/static/umap/js/umap.features.js +60 -40
  39. umap/static/umap/js/umap.forms.js +111 -25
  40. umap/static/umap/js/umap.icon.js +11 -4
  41. umap/static/umap/js/umap.importer.js +24 -17
  42. umap/static/umap/js/umap.js +170 -145
  43. umap/static/umap/js/umap.layer.js +71 -40
  44. umap/static/umap/js/umap.permissions.js +9 -11
  45. umap/static/umap/js/umap.popup.js +10 -21
  46. umap/static/umap/js/umap.share.js +11 -8
  47. umap/static/umap/js/umap.tableeditor.js +4 -6
  48. umap/static/umap/js/umap.ui.js +0 -51
  49. umap/static/umap/locale/am_ET.js +242 -227
  50. umap/static/umap/locale/am_ET.json +18 -7
  51. umap/static/umap/locale/ar.js +242 -227
  52. umap/static/umap/locale/ar.json +18 -7
  53. umap/static/umap/locale/ast.js +242 -227
  54. umap/static/umap/locale/ast.json +18 -7
  55. umap/static/umap/locale/bg.js +242 -227
  56. umap/static/umap/locale/bg.json +18 -7
  57. umap/static/umap/locale/br.js +252 -237
  58. umap/static/umap/locale/br.json +22 -11
  59. umap/static/umap/locale/ca.js +242 -227
  60. umap/static/umap/locale/ca.json +18 -7
  61. umap/static/umap/locale/cs_CZ.js +242 -227
  62. umap/static/umap/locale/cs_CZ.json +18 -7
  63. umap/static/umap/locale/da.js +242 -227
  64. umap/static/umap/locale/da.json +18 -7
  65. umap/static/umap/locale/de.js +242 -227
  66. umap/static/umap/locale/de.json +18 -7
  67. umap/static/umap/locale/el.js +242 -227
  68. umap/static/umap/locale/el.json +18 -7
  69. umap/static/umap/locale/en.js +242 -234
  70. umap/static/umap/locale/en.json +19 -8
  71. umap/static/umap/locale/en_US.json +18 -7
  72. umap/static/umap/locale/es.js +242 -227
  73. umap/static/umap/locale/es.json +18 -7
  74. umap/static/umap/locale/et.js +242 -227
  75. umap/static/umap/locale/et.json +18 -7
  76. umap/static/umap/locale/eu.js +227 -199
  77. umap/static/umap/locale/eu.json +1 -1
  78. umap/static/umap/locale/fa_IR.js +242 -227
  79. umap/static/umap/locale/fa_IR.json +18 -7
  80. umap/static/umap/locale/fi.js +242 -227
  81. umap/static/umap/locale/fi.json +18 -7
  82. umap/static/umap/locale/fr.js +242 -234
  83. umap/static/umap/locale/fr.json +18 -7
  84. umap/static/umap/locale/gl.js +242 -227
  85. umap/static/umap/locale/gl.json +18 -7
  86. umap/static/umap/locale/he.js +242 -227
  87. umap/static/umap/locale/he.json +18 -7
  88. umap/static/umap/locale/hr.js +242 -227
  89. umap/static/umap/locale/hr.json +18 -7
  90. umap/static/umap/locale/hu.js +242 -234
  91. umap/static/umap/locale/hu.json +18 -7
  92. umap/static/umap/locale/id.js +242 -227
  93. umap/static/umap/locale/id.json +18 -7
  94. umap/static/umap/locale/is.js +242 -227
  95. umap/static/umap/locale/is.json +18 -7
  96. umap/static/umap/locale/it.js +242 -234
  97. umap/static/umap/locale/it.json +18 -7
  98. umap/static/umap/locale/ja.js +242 -227
  99. umap/static/umap/locale/ja.json +18 -7
  100. umap/static/umap/locale/ko.js +242 -227
  101. umap/static/umap/locale/ko.json +18 -7
  102. umap/static/umap/locale/lt.js +242 -227
  103. umap/static/umap/locale/lt.json +18 -7
  104. umap/static/umap/locale/ms.js +242 -234
  105. umap/static/umap/locale/ms.json +19 -8
  106. umap/static/umap/locale/nl.js +245 -230
  107. umap/static/umap/locale/nl.json +18 -7
  108. umap/static/umap/locale/no.js +242 -227
  109. umap/static/umap/locale/no.json +18 -7
  110. umap/static/umap/locale/pl.js +242 -227
  111. umap/static/umap/locale/pl.json +18 -7
  112. umap/static/umap/locale/pl_PL.json +18 -7
  113. umap/static/umap/locale/pt.js +242 -227
  114. umap/static/umap/locale/pt.json +18 -7
  115. umap/static/umap/locale/pt_BR.js +242 -227
  116. umap/static/umap/locale/pt_BR.json +18 -7
  117. umap/static/umap/locale/pt_PT.js +242 -227
  118. umap/static/umap/locale/pt_PT.json +18 -7
  119. umap/static/umap/locale/ro.js +242 -227
  120. umap/static/umap/locale/ro.json +18 -7
  121. umap/static/umap/locale/ru.js +242 -227
  122. umap/static/umap/locale/ru.json +18 -7
  123. umap/static/umap/locale/si.js +1 -1
  124. umap/static/umap/locale/si.json +1 -1
  125. umap/static/umap/locale/sk_SK.js +242 -227
  126. umap/static/umap/locale/sk_SK.json +18 -7
  127. umap/static/umap/locale/sl.js +242 -227
  128. umap/static/umap/locale/sl.json +18 -7
  129. umap/static/umap/locale/sr.js +242 -227
  130. umap/static/umap/locale/sr.json +18 -7
  131. umap/static/umap/locale/sv.js +242 -227
  132. umap/static/umap/locale/sv.json +18 -7
  133. umap/static/umap/locale/th_TH.js +242 -227
  134. umap/static/umap/locale/th_TH.json +18 -7
  135. umap/static/umap/locale/tr.js +242 -227
  136. umap/static/umap/locale/tr.json +18 -7
  137. umap/static/umap/locale/uk_UA.js +242 -227
  138. umap/static/umap/locale/uk_UA.json +18 -7
  139. umap/static/umap/locale/vi.js +242 -227
  140. umap/static/umap/locale/vi.json +18 -7
  141. umap/static/umap/locale/vi_VN.json +18 -7
  142. umap/static/umap/locale/zh.js +242 -227
  143. umap/static/umap/locale/zh.json +18 -7
  144. umap/static/umap/locale/zh_CN.json +18 -7
  145. umap/static/umap/locale/zh_TW.Big5.json +18 -7
  146. umap/static/umap/locale/zh_TW.js +242 -234
  147. umap/static/umap/locale/zh_TW.json +18 -7
  148. umap/static/umap/map.css +114 -265
  149. umap/static/umap/test/DataLayer.js +463 -0
  150. umap/static/umap/test/Feature.js +0 -226
  151. umap/static/umap/test/TableEditor.js +104 -0
  152. umap/static/umap/test/Util.js +0 -521
  153. umap/static/umap/test/index.html +0 -1
  154. umap/static/umap/unittests/URLs.js +1 -1
  155. umap/static/umap/unittests/utils.js +610 -0
  156. umap/static/umap/vars.css +9 -0
  157. umap/static/umap/vendors/dompurify/purify.es.mjs +1525 -0
  158. umap/static/umap/vendors/iconlayers/iconLayers.js +1 -1
  159. umap/templates/umap/css.html +2 -0
  160. umap/templates/umap/js.html +0 -1
  161. umap/templates/umap/map_detail.html +4 -0
  162. umap/templates/umap/map_table.html +12 -10
  163. umap/templatetags/umap_tags.py +5 -0
  164. umap/tests/integration/conftest.py +12 -1
  165. umap/tests/integration/test_anonymous_owned_map.py +6 -5
  166. umap/tests/integration/test_browser.py +12 -25
  167. umap/tests/integration/test_choropleth.py +1 -1
  168. umap/tests/integration/test_dashboard.py +10 -0
  169. umap/tests/integration/test_datalayer.py +8 -6
  170. umap/tests/integration/test_edit_datalayer.py +24 -19
  171. umap/tests/integration/test_edit_map.py +182 -2
  172. umap/tests/integration/test_edit_marker.py +120 -0
  173. umap/tests/integration/test_edit_polygon.py +122 -0
  174. umap/tests/integration/test_facets_browser.py +104 -14
  175. umap/tests/integration/test_import.py +70 -20
  176. umap/tests/integration/test_map.py +19 -17
  177. umap/tests/integration/test_map_list.py +28 -0
  178. umap/tests/integration/test_owned_map.py +10 -10
  179. umap/tests/integration/test_picto.py +5 -5
  180. umap/tests/integration/test_querystring.py +9 -15
  181. umap/tests/integration/test_slideshow.py +0 -5
  182. umap/tests/integration/test_statics.py +3 -2
  183. umap/tests/integration/test_tableeditor.py +1 -5
  184. umap/tests/integration/test_view_marker.py +64 -0
  185. umap/tests/integration/test_view_polygon.py +59 -0
  186. umap/tests/integration/test_view_polyline.py +51 -0
  187. umap/tests/test_map_views.py +13 -0
  188. {umap_project-2.1.3.dist-info → umap_project-2.2.0b0.dist-info}/METADATA +8 -8
  189. {umap_project-2.1.3.dist-info → umap_project-2.2.0b0.dist-info}/RECORD +194 -178
  190. umap/static/umap/vendors/dompurify/purify.min.js +0 -3
  191. umap/static/umap/vendors/dompurify/purify.min.js.map +0 -1
  192. /umap/tests/integration/{test_polygon.py → test_draw_polygon.py} +0 -0
  193. /umap/tests/integration/{test_polyline.py → test_draw_polyline.py} +0 -0
  194. {umap_project-2.1.3.dist-info → umap_project-2.2.0b0.dist-info}/WHEEL +0 -0
  195. {umap_project-2.1.3.dist-info → umap_project-2.2.0b0.dist-info}/entry_points.txt +0 -0
  196. {umap_project-2.1.3.dist-info → umap_project-2.2.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,4 @@
1
- // Vendorized from leaflet.utils
2
- // https://github.com/Leaflet/Leaflet/blob/108c6717b70f57c63645498f9bd66b6677758786/src/core/Util.js#L132-L151
3
- var templateRe = /\{ *([\w_ -]+) *\}/g
4
-
5
- function template(str, data) {
6
- return str.replace(templateRe, function (str, key) {
7
- var value = data[key]
8
-
9
- if (value === undefined) {
10
- throw new Error('No value provided for variable ' + str)
11
- } else if (typeof value === 'function') {
12
- value = value(data)
13
- }
14
- return value
15
- })
16
- }
1
+ import { template } from "./utils.js"
17
2
 
18
3
  export default class URLs {
19
4
  constructor(serverUrls) {
@@ -1,3 +1,5 @@
1
+ import { default as DOMPurifyInitializer } from '../../vendors/dompurify/purify.es.mjs'
2
+
1
3
  /**
2
4
  * Generate a pseudo-unique identifier (5 chars long, mixed-case alphanumeric)
3
5
  *
@@ -22,3 +24,341 @@ export function checkId(string) {
22
24
  if (typeof string !== 'string') return false
23
25
  return /^[A-Za-z0-9]{5}$/.test(string)
24
26
  }
27
+
28
+ /**
29
+ * Compute the impacts for a given list of fields.
30
+ *
31
+ * Return an array of unique impacts.
32
+ *
33
+ * @param {fields} list[fields]
34
+ * @returns Array[string]
35
+ */
36
+ export function getImpactsFromSchema(fields, schema) {
37
+ schema = schema || U.SCHEMA
38
+ let impacted = fields
39
+ .map((field) => {
40
+ // remove the option prefix for fields
41
+ // And only keep the first part in case of a subfield
42
+ // (e.g "options.limitBounds.foobar" will just return "limitBounds")
43
+ return field.replace('options.', '').split('.')[0]
44
+ })
45
+ .reduce((acc, field) => {
46
+ // retrieve the "impacts" field from the schema
47
+ // and merge them together using sets
48
+ const impacts = schema[field]?.impacts || []
49
+ impacts.forEach((impact) => acc.add(impact))
50
+ return acc
51
+ }, new Set())
52
+
53
+ return Array.from(impacted)
54
+ }
55
+
56
+ /**
57
+ * Import DOM purify, and initialize it.
58
+ *
59
+ * If the context is a node server, uses jsdom to provide
60
+ * DOM APIs
61
+ */
62
+ export default function getPurify() {
63
+ if (typeof window === 'undefined') {
64
+ return DOMPurifyInitializer(new global.JSDOM('').window)
65
+ } else {
66
+ return DOMPurifyInitializer(window)
67
+ }
68
+ }
69
+
70
+ export function escapeHTML(s) {
71
+ s = s ? s.toString() : ''
72
+ s = getPurify().sanitize(s, {
73
+ USE_PROFILES: { html: true },
74
+ ADD_TAGS: ['iframe'],
75
+ ALLOWED_TAGS: [
76
+ 'h3',
77
+ 'h4',
78
+ 'h5',
79
+ 'hr',
80
+ 'strong',
81
+ 'em',
82
+ 'ul',
83
+ 'li',
84
+ 'a',
85
+ 'div',
86
+ 'iframe',
87
+ 'img',
88
+ 'br',
89
+ ],
90
+ ADD_ATTR: ['target', 'allow', 'allowfullscreen', 'frameborder', 'scrolling'],
91
+ ALLOWED_ATTR: ['href', 'src', 'width', 'height'],
92
+ // Added: `geo:` URL scheme as defined in RFC5870:
93
+ // https://www.rfc-editor.org/rfc/rfc5870.html
94
+ // The base RegExp comes from:
95
+ // https://github.com/cure53/DOMPurify/blob/main/src/regexp.js#L10
96
+ ALLOWED_URI_REGEXP:
97
+ /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|geo):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
98
+ })
99
+ return s
100
+ }
101
+
102
+ export function toHTML(r, options) {
103
+ if (!r) return ''
104
+ const target = (options && options.target) || 'blank'
105
+ let ii
106
+
107
+ // detect newline format
108
+ const newline = r.indexOf('\r\n') != -1 ? '\r\n' : r.indexOf('\n') != -1 ? '\n' : ''
109
+
110
+ // headings and hr
111
+ r = r.replace(/^### (.*)/gm, '<h5>$1</h5>')
112
+ r = r.replace(/^## (.*)/gm, '<h4>$1</h4>')
113
+ r = r.replace(/^# (.*)/gm, '<h3>$1</h3>')
114
+ r = r.replace(/^---/gm, '<hr>')
115
+
116
+ // bold, italics
117
+ r = r.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
118
+ r = r.replace(/\*(.*?)\*/g, '<em>$1</em>')
119
+
120
+ // unordered lists
121
+ r = r.replace(/^\*\* (.*)/gm, '<ul><ul><li>$1</li></ul></ul>')
122
+ r = r.replace(/^\* (.*)/gm, '<ul><li>$1</li></ul>')
123
+ for (ii = 0; ii < 3; ii++)
124
+ r = r.replace(new RegExp(`</ul>${newline}<ul>`, 'g'), newline)
125
+
126
+ // links
127
+ r = r.replace(/(\[\[http)/g, '[[h_t_t_p') // Escape for avoiding clash between [[http://xxx]] and http://xxx
128
+ r = r.replace(/({{http)/g, '{{h_t_t_p')
129
+ r = r.replace(/(=http)/g, '=h_t_t_p') // http://xxx as query string, see https://github.com/umap-project/umap/issues/607
130
+ r = r.replace(/(https?:[^ \<)\n]*)/g, `<a target="_${target}" href="$1">$1</a>`)
131
+ r = r.replace(
132
+ /\[\[(h_t_t_ps?:[^\]|]*?)\]\]/g,
133
+ `<a target="_${target}" href="$1">$1</a>`
134
+ )
135
+ r = r.replace(
136
+ /\[\[(h_t_t_ps?:[^|]*?)\|(.*?)\]\]/g,
137
+ `<a target="_${target}" href="$1">$2</a>`
138
+ )
139
+ r = r.replace(/\[\[([^\]|]*?)\]\]/g, `<a target="_${target}" href="$1">$1</a>`)
140
+ r = r.replace(/\[\[([^|]*?)\|(.*?)\]\]/g, `<a target="_${target}" href="$1">$2</a>`)
141
+
142
+ // iframe
143
+ r = r.replace(
144
+ /{{{(h_t_t_ps?[^ |{]*)}}}/g,
145
+ '<div><iframe frameborder="0" src="$1" width="100%" height="300px"></iframe></div>'
146
+ )
147
+ r = r.replace(
148
+ /{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?}}}/g,
149
+ '<div><iframe frameborder="0" src="$1" width="100%" height="$2px"></iframe></div>'
150
+ )
151
+ r = r.replace(
152
+ /{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?\*(\d*)(px)?}}}/g,
153
+ '<div><iframe frameborder="0" src="$1" width="$4px" height="$2px"></iframe></div>'
154
+ )
155
+
156
+ // images
157
+ r = r.replace(/{{([^\]|]*?)}}/g, '<img src="$1">')
158
+ r = r.replace(
159
+ /{{([^|]*?)\|(\d*?)(px)?}}/g,
160
+ '<img src="$1" style="width:$2px;min-width:$2px;">'
161
+ )
162
+
163
+ //Unescape http
164
+ r = r.replace(/(h_t_t_p)/g, 'http')
165
+
166
+ // Preserver line breaks
167
+ if (newline) r = r.replace(new RegExp(`${newline}(?=[^]+)`, 'g'), `<br>${newline}`)
168
+
169
+ r = escapeHTML(r)
170
+
171
+ return r
172
+ }
173
+
174
+ export function isObject(what) {
175
+ return typeof what === 'object' && what !== null
176
+ }
177
+
178
+ export function CopyJSON(geojson) {
179
+ return JSON.parse(JSON.stringify(geojson))
180
+ }
181
+
182
+ export function detectFileType(f) {
183
+ const filename = f.name ? escape(f.name.toLowerCase()) : ''
184
+ function ext(_) {
185
+ return filename.indexOf(_) !== -1
186
+ }
187
+ if (f.type === 'application/vnd.google-earth.kml+xml' || ext('.kml')) {
188
+ return 'kml'
189
+ }
190
+ if (ext('.gpx')) return 'gpx'
191
+ if (ext('.geojson') || ext('.json')) return 'geojson'
192
+ if (f.type === 'text/csv' || ext('.csv') || ext('.tsv') || ext('.dsv')) {
193
+ return 'csv'
194
+ }
195
+ if (ext('.xml') || ext('.osm')) return 'osm'
196
+ if (ext('.umap')) return 'umap'
197
+ }
198
+
199
+ export function usableOption(options, option) {
200
+ return options[option] !== undefined && options[option] !== ''
201
+ }
202
+
203
+ /**
204
+ * Processes a template string by replacing placeholders with corresponding
205
+ * data values.
206
+ *
207
+ * Supports dot notation for nested objects and optional static fallbacks.
208
+ * If a value is not found, the placeholder is replaced with an empty string
209
+ * or left unchanged when 'ignore' is true.
210
+ *
211
+ * @param {string} str - The template string with placeholders.
212
+ * @param {Object} data - The data object from which to pull replacement values.
213
+ * @param {boolean} [ignore=false] - If true, leaves placeholders unchanged when no value is found.
214
+ * @returns {string} The processed string with placeholders replaced.
215
+ */
216
+ export function greedyTemplate(str, data, ignore) {
217
+ function getValue(data, path) {
218
+ let value = data
219
+ for (let i = 0; i < path.length; i++) {
220
+ value = value[path[i]]
221
+ if (value === undefined) break
222
+ }
223
+ return value
224
+ }
225
+
226
+ if (typeof str !== 'string') return ''
227
+
228
+ return str.replace(
229
+ /\{ *([^\{\}/\-]+)(?:\|("[^"]*"))? *\}/g,
230
+ (str, key, staticFallback) => {
231
+ const vars = key.split('|')
232
+ let value
233
+ let path
234
+ if (staticFallback !== undefined) {
235
+ vars.push(staticFallback)
236
+ }
237
+ for (let i = 0; i < vars.length; i++) {
238
+ path = vars[i]
239
+ if (path.startsWith('"') && path.endsWith('"'))
240
+ value = path.substring(1, path.length - 1) // static default value.
241
+ else value = getValue(data, path.split('.'))
242
+ if (value !== undefined) break
243
+ }
244
+ if (value === undefined) {
245
+ if (ignore) value = str
246
+ else value = ''
247
+ }
248
+ return value
249
+ }
250
+ )
251
+ }
252
+
253
+ export function naturalSort(a, b, lang) {
254
+ return a
255
+ .toString()
256
+ .toLowerCase()
257
+ .localeCompare(b.toString().toLowerCase(), lang || 'en', {
258
+ sensitivity: 'base',
259
+ numeric: true,
260
+ })
261
+ }
262
+
263
+ export function sortFeatures(features, sortKey, lang) {
264
+ const sortKeys = (sortKey || 'name').split(',')
265
+
266
+ const sort = (a, b, i) => {
267
+ let sortKey = sortKeys[i],
268
+ reverse = 1
269
+ if (sortKey[0] === '-') {
270
+ reverse = -1
271
+ sortKey = sortKey.substring(1)
272
+ }
273
+ let score
274
+ const valA = a.properties[sortKey] || ''
275
+ const valB = b.properties[sortKey] || ''
276
+ if (!valA) score = -1
277
+ else if (!valB) score = 1
278
+ else score = naturalSort(valA, valB, lang)
279
+ if (score === 0 && sortKeys[i + 1]) return sort(a, b, i + 1)
280
+ return score * reverse
281
+ }
282
+
283
+ features.sort((a, b) => {
284
+ if (!a.properties || !b.properties) {
285
+ return 0
286
+ }
287
+ return sort(a, b, 0)
288
+ })
289
+
290
+ return features
291
+ }
292
+
293
+ export function flattenCoordinates(coords) {
294
+ while (coords[0] && typeof coords[0][0] !== 'number') coords = coords[0]
295
+ return coords
296
+ }
297
+
298
+ export function buildQueryString(params) {
299
+ const query_string = []
300
+ for (const key in params) {
301
+ query_string.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
302
+ }
303
+ return query_string.join('&')
304
+ }
305
+
306
+ export function getBaseUrl() {
307
+ return `//${window.location.host}${window.location.pathname}`
308
+ }
309
+
310
+ export function hasVar(value) {
311
+ return typeof value === 'string' && value.indexOf('{') != -1
312
+ }
313
+
314
+ export function isPath(value) {
315
+ return value && value.length && value.startsWith('/')
316
+ }
317
+
318
+ export function isRemoteUrl(value) {
319
+ return value && value.length && value.startsWith('http')
320
+ }
321
+
322
+ export function isDataImage(value) {
323
+ return value && value.length && value.startsWith('data:image')
324
+ }
325
+
326
+ /**
327
+ * Normalizes the input string by converting to lowercase
328
+ * and removing diacritics.
329
+ *
330
+ * If the input is nullish, it defaults to an empty string.
331
+ *
332
+ * @param {string} s - The string to be normalized.
333
+ * @returns {string} - The normalized string with lowercase
334
+ * characters and no diacritics.
335
+ */
336
+ export function normalize(s) {
337
+ return (s || '')
338
+ .toLowerCase()
339
+ .normalize('NFD')
340
+ .replace(/[\u0300-\u036f]/g, '')
341
+ }
342
+
343
+ // Vendorized from leaflet.utils
344
+ // https://github.com/Leaflet/Leaflet/blob/108c6717b70f57c63645498f9bd66b6677758786/src/core/Util.js#L132-L151
345
+ var templateRe = /\{ *([\w_ -]+) *\}/g
346
+
347
+ export function template(str, data) {
348
+ return str.replace(templateRe, function (str, key) {
349
+ var value = data[key]
350
+
351
+ if (value === undefined) {
352
+ throw new Error('No value provided for variable ' + str)
353
+ } else if (typeof value === 'function') {
354
+ value = value(data)
355
+ }
356
+ return value
357
+ })
358
+ }
359
+
360
+ export function parseNaiveDate(value) {
361
+ const naive = new Date(value)
362
+ // Let's pretend naive date are UTC, and remove time…
363
+ return new Date(Date.UTC(naive.getFullYear(), naive.getMonth(), naive.getDate()))
364
+ }