umap-project 3.2.0__py3-none-any.whl → 3.3.0__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 (173) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/en/LC_MESSAGES/django.po +15 -15
  4. umap/settings/base.py +2 -0
  5. umap/static/umap/css/contextmenu.css +58 -2
  6. umap/static/umap/css/form.css +175 -45
  7. umap/static/umap/css/icon.css +20 -0
  8. umap/static/umap/img/16-white.svg +21 -40
  9. umap/static/umap/img/16.svg +1 -1
  10. umap/static/umap/img/24-white.svg +9 -9
  11. umap/static/umap/img/24.svg +23 -10
  12. umap/static/umap/img/source/16-white.svg +23 -41
  13. umap/static/umap/img/source/16.svg +1 -1
  14. umap/static/umap/img/source/24-white.svg +11 -11
  15. umap/static/umap/img/source/24.svg +25 -12
  16. umap/static/umap/js/modules/caption.js +8 -0
  17. umap/static/umap/js/modules/data/features.js +317 -173
  18. umap/static/umap/js/modules/data/layer.js +17 -18
  19. umap/static/umap/js/modules/form/builder.js +11 -7
  20. umap/static/umap/js/modules/form/fields.js +10 -7
  21. umap/static/umap/js/modules/formatter.js +42 -20
  22. umap/static/umap/js/modules/importer.js +6 -1
  23. umap/static/umap/js/modules/importers/opendata.js +125 -37
  24. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  25. umap/static/umap/js/modules/managers.js +12 -4
  26. umap/static/umap/js/modules/printer.js +107 -0
  27. umap/static/umap/js/modules/rendering/controls.js +78 -2
  28. umap/static/umap/js/modules/rendering/icon.js +113 -82
  29. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  30. umap/static/umap/js/modules/rendering/map.js +5 -1
  31. umap/static/umap/js/modules/rendering/template.js +71 -1
  32. umap/static/umap/js/modules/rendering/ui.js +98 -34
  33. umap/static/umap/js/modules/schema.js +24 -0
  34. umap/static/umap/js/modules/share.js +19 -12
  35. umap/static/umap/js/modules/ui/bar.js +6 -1
  36. umap/static/umap/js/modules/ui/base.js +24 -9
  37. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  38. umap/static/umap/js/modules/ui/dialog.js +7 -4
  39. umap/static/umap/js/modules/umap.js +67 -61
  40. umap/static/umap/js/umap.controls.js +22 -57
  41. umap/static/umap/locale/am_ET.js +39 -4
  42. umap/static/umap/locale/am_ET.json +39 -4
  43. umap/static/umap/locale/ar.js +39 -4
  44. umap/static/umap/locale/ar.json +39 -4
  45. umap/static/umap/locale/ast.js +39 -4
  46. umap/static/umap/locale/ast.json +39 -4
  47. umap/static/umap/locale/bg.js +39 -4
  48. umap/static/umap/locale/bg.json +39 -4
  49. umap/static/umap/locale/br.js +39 -4
  50. umap/static/umap/locale/br.json +39 -4
  51. umap/static/umap/locale/ca.js +39 -4
  52. umap/static/umap/locale/ca.json +39 -4
  53. umap/static/umap/locale/cs_CZ.js +39 -4
  54. umap/static/umap/locale/cs_CZ.json +39 -4
  55. umap/static/umap/locale/da.js +47 -12
  56. umap/static/umap/locale/da.json +47 -12
  57. umap/static/umap/locale/de.js +39 -4
  58. umap/static/umap/locale/de.json +39 -4
  59. umap/static/umap/locale/el.js +39 -4
  60. umap/static/umap/locale/el.json +39 -4
  61. umap/static/umap/locale/en.js +39 -4
  62. umap/static/umap/locale/en.json +39 -4
  63. umap/static/umap/locale/en_US.json +39 -4
  64. umap/static/umap/locale/es.js +47 -12
  65. umap/static/umap/locale/es.json +47 -12
  66. umap/static/umap/locale/et.js +39 -4
  67. umap/static/umap/locale/et.json +39 -4
  68. umap/static/umap/locale/eu.js +79 -44
  69. umap/static/umap/locale/eu.json +79 -44
  70. umap/static/umap/locale/fa_IR.js +39 -4
  71. umap/static/umap/locale/fa_IR.json +39 -4
  72. umap/static/umap/locale/fi.js +39 -4
  73. umap/static/umap/locale/fi.json +39 -4
  74. umap/static/umap/locale/fr.js +39 -4
  75. umap/static/umap/locale/fr.json +39 -4
  76. umap/static/umap/locale/gl.js +39 -4
  77. umap/static/umap/locale/gl.json +39 -4
  78. umap/static/umap/locale/he.js +39 -4
  79. umap/static/umap/locale/he.json +39 -4
  80. umap/static/umap/locale/hr.js +39 -4
  81. umap/static/umap/locale/hr.json +39 -4
  82. umap/static/umap/locale/hu.js +39 -4
  83. umap/static/umap/locale/hu.json +39 -4
  84. umap/static/umap/locale/id.js +39 -4
  85. umap/static/umap/locale/id.json +39 -4
  86. umap/static/umap/locale/is.js +39 -4
  87. umap/static/umap/locale/is.json +39 -4
  88. umap/static/umap/locale/it.js +39 -4
  89. umap/static/umap/locale/it.json +39 -4
  90. umap/static/umap/locale/ja.js +39 -4
  91. umap/static/umap/locale/ja.json +39 -4
  92. umap/static/umap/locale/ko.js +39 -4
  93. umap/static/umap/locale/ko.json +39 -4
  94. umap/static/umap/locale/lt.js +39 -4
  95. umap/static/umap/locale/lt.json +39 -4
  96. umap/static/umap/locale/ms.js +39 -4
  97. umap/static/umap/locale/ms.json +39 -4
  98. umap/static/umap/locale/nl.js +39 -4
  99. umap/static/umap/locale/nl.json +39 -4
  100. umap/static/umap/locale/no.js +39 -4
  101. umap/static/umap/locale/no.json +39 -4
  102. umap/static/umap/locale/pl.js +39 -4
  103. umap/static/umap/locale/pl.json +39 -4
  104. umap/static/umap/locale/pl_PL.json +39 -4
  105. umap/static/umap/locale/pt.js +39 -4
  106. umap/static/umap/locale/pt.json +39 -4
  107. umap/static/umap/locale/pt_BR.js +39 -4
  108. umap/static/umap/locale/pt_BR.json +39 -4
  109. umap/static/umap/locale/pt_PT.js +39 -4
  110. umap/static/umap/locale/pt_PT.json +39 -4
  111. umap/static/umap/locale/ro.js +39 -4
  112. umap/static/umap/locale/ro.json +39 -4
  113. umap/static/umap/locale/ru.js +39 -4
  114. umap/static/umap/locale/ru.json +39 -4
  115. umap/static/umap/locale/sk_SK.js +39 -4
  116. umap/static/umap/locale/sk_SK.json +39 -4
  117. umap/static/umap/locale/sl.js +39 -4
  118. umap/static/umap/locale/sl.json +39 -4
  119. umap/static/umap/locale/sr.js +39 -4
  120. umap/static/umap/locale/sr.json +39 -4
  121. umap/static/umap/locale/sv.js +39 -4
  122. umap/static/umap/locale/sv.json +39 -4
  123. umap/static/umap/locale/th_TH.js +39 -4
  124. umap/static/umap/locale/th_TH.json +39 -4
  125. umap/static/umap/locale/tr.js +39 -4
  126. umap/static/umap/locale/tr.json +39 -4
  127. umap/static/umap/locale/uk_UA.js +39 -4
  128. umap/static/umap/locale/uk_UA.json +39 -4
  129. umap/static/umap/locale/vi.js +39 -4
  130. umap/static/umap/locale/vi.json +39 -4
  131. umap/static/umap/locale/vi_VN.json +39 -4
  132. umap/static/umap/locale/zh.js +39 -4
  133. umap/static/umap/locale/zh.json +39 -4
  134. umap/static/umap/locale/zh_CN.json +39 -4
  135. umap/static/umap/locale/zh_TW.Big5.json +39 -4
  136. umap/static/umap/locale/zh_TW.js +98 -63
  137. umap/static/umap/locale/zh_TW.json +98 -63
  138. umap/static/umap/map.css +90 -41
  139. umap/static/umap/vars.css +1 -0
  140. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  141. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  142. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  143. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  144. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  145. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  146. umap/storage/staticfiles.py +12 -0
  147. umap/templates/umap/css.html +0 -4
  148. umap/templates/umap/js.html +1 -3
  149. umap/tests/integration/test_basics.py +2 -0
  150. umap/tests/integration/test_conditional_rules.py +17 -17
  151. umap/tests/integration/test_datalayer.py +1 -1
  152. umap/tests/integration/test_draw_polygon.py +3 -5
  153. umap/tests/integration/test_draw_polyline.py +4 -6
  154. umap/tests/integration/test_draw_route.py +178 -0
  155. umap/tests/integration/test_edit_map.py +1 -1
  156. umap/tests/integration/test_edit_marker.py +7 -7
  157. umap/tests/integration/test_edit_polygon.py +2 -2
  158. umap/tests/integration/test_export_map.py +74 -10
  159. umap/tests/integration/test_map_preview.py +1 -1
  160. umap/tests/integration/test_share.py +1 -1
  161. umap/tests/integration/test_tableeditor.py +4 -4
  162. umap/tests/integration/test_websocket_sync.py +4 -4
  163. umap/utils.py +5 -1
  164. umap/views.py +2 -0
  165. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/METADATA +8 -8
  166. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/RECORD +169 -165
  167. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  168. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  169. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  170. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  171. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  172. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  173. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -298,7 +298,7 @@ export class DataLayer {
298
298
  }
299
299
 
300
300
  dataChanged() {
301
- if (!this.isLoaded()) return
301
+ if (!this.isLoaded() || this._batch) return
302
302
  this._umap.onDataLayersChanged()
303
303
  this.layer.dataChanged()
304
304
  }
@@ -547,22 +547,27 @@ export class DataLayer {
547
547
  }
548
548
 
549
549
  sortedValues(property) {
550
- return Array.from(this.features.values())
550
+ return this.features
551
+ .all()
551
552
  .map((feature) => feature.properties[property])
552
553
  .filter((val, idx, arr) => arr.indexOf(val) === idx)
553
554
  .sort(Utils.naturalSort)
554
555
  }
555
556
 
556
557
  addData(geojson, sync) {
558
+ let data = []
559
+ this._batch = true
557
560
  try {
558
561
  // Do not fail if remote data is somehow invalid,
559
562
  // otherwise the layer becomes uneditable.
560
- return this.makeFeatures(geojson, sync)
563
+ data = this.makeFeatures(geojson, sync)
561
564
  } catch (err) {
562
565
  console.debug('Error with DataLayer', this.id)
563
566
  console.error(err)
564
- return []
565
567
  }
568
+ this._batch = false
569
+ this.dataChanged()
570
+ return data
566
571
  }
567
572
 
568
573
  makeFeatures(geojson = {}, sync = true) {
@@ -821,6 +826,7 @@ export class DataLayer {
821
826
  const fields = [
822
827
  'properties.color',
823
828
  'properties.iconClass',
829
+ 'properties.iconSize',
824
830
  'properties.iconUrl',
825
831
  'properties.iconOpacity',
826
832
  'properties.opacity',
@@ -1176,12 +1182,6 @@ export class DataLayer {
1176
1182
  })
1177
1183
  }
1178
1184
 
1179
- featuresToGeoJSON() {
1180
- const features = []
1181
- this.features.forEach((feature) => features.push(feature.toGeoJSON()))
1182
- return features
1183
- }
1184
-
1185
1185
  async show() {
1186
1186
  this._leafletMap.addLayer(this.layer)
1187
1187
  if (!this.isLoaded()) await this.fetchData()
@@ -1194,10 +1194,11 @@ export class DataLayer {
1194
1194
  }
1195
1195
 
1196
1196
  toggle(force) {
1197
- // From now on, do not try to how/hidedataChanged
1198
- // automatically this layer.
1199
- let display = force
1197
+ // From now on, do not try to how/hide
1198
+ // automatically this layer, as user
1199
+ // has taken control on this.
1200
1200
  this._forcedVisibility = true
1201
+ let display = force
1201
1202
  if (force === undefined) {
1202
1203
  if (!this.isVisible()) display = true
1203
1204
  else display = false
@@ -1267,11 +1268,9 @@ export class DataLayer {
1267
1268
  }
1268
1269
 
1269
1270
  umapGeoJSON() {
1270
- return {
1271
- type: 'FeatureCollection',
1272
- features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
1273
- _umap_options: this.properties,
1274
- }
1271
+ const geojson = this._umap.formatter.toFeatureCollection(this.features.all())
1272
+ geojson._umap_options = this.properties
1273
+ return geojson
1275
1274
  }
1276
1275
 
1277
1276
  getDOMOrder() {
@@ -70,7 +70,11 @@ export class Form extends Utils.WithEvents {
70
70
  }
71
71
 
72
72
  setter(field, value) {
73
- Utils.setObjectValue(this.obj, field, value)
73
+ if ('setter' in this.obj) {
74
+ this.obj.setter(field, value)
75
+ } else {
76
+ Utils.setObjectValue(this.obj, field, value)
77
+ }
74
78
  }
75
79
 
76
80
  restoreField(field) {
@@ -104,9 +108,13 @@ export class Form extends Utils.WithEvents {
104
108
  finish() {}
105
109
 
106
110
  getTemplate(helper) {
111
+ let tpl = helper.getTemplate()
112
+ if (helper.properties.label && !tpl.includes(helper.properties.label)) {
113
+ tpl = `<label>${helper.properties.label}${tpl}</label>`
114
+ }
107
115
  return `
108
116
  <div class="formbox" data-ref=container>
109
- ${helper.getTemplate()}
117
+ ${tpl}
110
118
  <small class="help-text" data-ref=helpText></small>
111
119
  </div>`
112
120
  }
@@ -171,11 +179,7 @@ export class MutatingForm extends Form {
171
179
 
172
180
  setter(field, value) {
173
181
  const oldValue = this.getter(field)
174
- if ('setter' in this.obj) {
175
- this.obj.setter(field, value)
176
- } else {
177
- super.setter(field, value)
178
- }
182
+ super.setter(field, value)
179
183
  if ('render' in this.obj) {
180
184
  this.obj.render([field], this)
181
185
  }
@@ -582,6 +582,9 @@ Fields.SlideshowDelay = class extends Fields.IntSelect {
582
582
  Fields.DataLayerSwitcher = class extends Fields.Select {
583
583
  getOptions() {
584
584
  const options = []
585
+ if (this.properties.allowEmpty) {
586
+ options.push([null, translate('Import in a new layer')])
587
+ }
585
588
  this.builder._umap.datalayers.reverse().map((datalayer) => {
586
589
  if (
587
590
  datalayer.isLoaded() &&
@@ -595,7 +598,7 @@ Fields.DataLayerSwitcher = class extends Fields.Select {
595
598
  }
596
599
 
597
600
  toHTML() {
598
- return this.obj.datalayer.id
601
+ return this.obj.datalayer?.id
599
602
  }
600
603
 
601
604
  toJS() {
@@ -604,7 +607,7 @@ Fields.DataLayerSwitcher = class extends Fields.Select {
604
607
 
605
608
  set() {
606
609
  this.builder._umap.lastUsedDataLayer = this.toJS()
607
- this.obj.changeDataLayer(this.toJS())
610
+ this.builder.setter(this.field, this.toJS())
608
611
  }
609
612
  }
610
613
 
@@ -863,6 +866,7 @@ Fields.IconUrl = class extends Fields.BlurInput {
863
866
  const loadCollection = (name) => {
864
867
  icons.innerHTML = ''
865
868
  const collection = this.pictogramCollections[name || collectionsNames[0]]
869
+ if (!collection) return
866
870
  const sorted = Object.entries(collection.categories).sort(([a], [b]) =>
867
871
  Utils.naturalSort(a, b, U.lang)
868
872
  )
@@ -1303,11 +1307,10 @@ Fields.Range = class extends Fields.FloatInput {
1303
1307
  const step = this.properties.step || 1
1304
1308
  const digits = step < 1 ? 1 : 0
1305
1309
  const id = `range-${this.properties.label || this.name}`
1306
- for (
1307
- let i = this.properties.min;
1308
- i <= this.properties.max;
1309
- i += (this.properties.max - this.properties.min) / 10
1310
- ) {
1310
+ const range = this.properties.max - this.properties.min
1311
+ const ticks = this.properties.ticks || Math.min(20, range / step)
1312
+ const tickStep = range / ticks
1313
+ for (let i = this.properties.min; i <= this.properties.max; i += tickStep) {
1311
1314
  const ii = i.toFixed(digits)
1312
1315
  options += `<option value="${ii}" label="${ii}"></option>`
1313
1316
  }
@@ -17,33 +17,18 @@ const parseTextGeom = async (geom) => {
17
17
 
18
18
  export const EXPORT_FORMATS = {
19
19
  geojson: {
20
- formatter: async (umap) => JSON.stringify(umap.toGeoJSON(), null, 2),
21
20
  ext: '.geojson',
22
21
  filetype: 'application/json',
23
22
  },
24
23
  gpx: {
25
- formatter: async (umap) => await umap.formatter.toGPX(umap.toGeoJSON()),
26
24
  ext: '.gpx',
27
25
  filetype: 'application/gpx+xml',
28
26
  },
29
27
  kml: {
30
- formatter: async (umap) => await umap.formatter.toKML(umap.toGeoJSON()),
31
28
  ext: '.kml',
32
29
  filetype: 'application/vnd.google-earth.kml+xml',
33
30
  },
34
31
  csv: {
35
- formatter: async (umap) => {
36
- const table = []
37
- umap.eachFeature((feature) => {
38
- const row = feature.toGeoJSON().properties
39
- const center = feature.center
40
- delete row._umap_options
41
- row.Latitude = center.lat
42
- row.Longitude = center.lng
43
- table.push(row)
44
- })
45
- return csv2geojson.dsv.csvFormat(table)
46
- },
47
32
  ext: '.csv',
48
33
  filetype: 'text/csv',
49
34
  },
@@ -180,17 +165,54 @@ export class Formatter {
180
165
  }
181
166
  }
182
167
 
183
- async toGPX(geojson) {
168
+ async stringify(features, format) {
169
+ switch (format) {
170
+ case 'csv':
171
+ return await this.toCSV(features)
172
+ case 'gpx':
173
+ return await this.toGPX(features)
174
+ case 'kml':
175
+ return await this.toKML(features)
176
+ case 'geojson':
177
+ return await this.toGeoJSON(features)
178
+ }
179
+ }
180
+
181
+ async toGPX(features) {
184
182
  const togpx = await import('../../vendors/geojson-to-gpx/index.js')
185
- for (const feature of geojson.features) {
183
+ for (const feature of features) {
186
184
  feature.properties.desc = feature.properties.description
187
185
  }
188
- const gpx = togpx.default(geojson)
186
+ const gpx = togpx.default(this.toFeatureCollection(features))
189
187
  return new XMLSerializer().serializeToString(gpx)
190
188
  }
191
189
 
192
- async toKML(geojson) {
190
+ async toKML(features) {
193
191
  const tokml = await import('../../vendors/tokml/tokml.es.js')
194
- return tokml.toKML(geojson)
192
+ return tokml.toKML(this.toFeatureCollection(features))
193
+ }
194
+
195
+ toFeatureCollection(features) {
196
+ return {
197
+ type: 'FeatureCollection',
198
+ features: features.map((f) => f.toGeoJSON()),
199
+ }
200
+ }
201
+
202
+ async toGeoJSON(features) {
203
+ return JSON.stringify(this.toFeatureCollection(features), null, 2)
204
+ }
205
+
206
+ async toCSV(features) {
207
+ const table = []
208
+ for (const feature of features) {
209
+ const row = feature.toGeoJSON().properties
210
+ const center = feature.center
211
+ delete row._umap_options
212
+ row.Latitude = center.lat
213
+ row.Longitude = center.lng
214
+ table.push(row)
215
+ }
216
+ return csv2geojson.dsv.csvFormat(table)
195
217
  }
196
218
  }
@@ -17,7 +17,7 @@ const TEMPLATE = `
17
17
  <input type="file" multiple autofocus onchange />
18
18
  <textarea onchange placeholder="${translate('Paste your data here')}"></textarea>
19
19
  <input class="highlightable" type="url" placeholder="${translate('Provide an URL here')}" onchange />
20
- <button class="flat importers" hidden data-ref="importersButton"><i class="icon icon-16 icon-magic"></i>${translate('Import helpers')}</button>
20
+ <button type=button class="importers" hidden data-ref="importersButton"><i class="icon icon-16 icon-magic"></i>${translate('Import helpers')}</button>
21
21
  </fieldset>
22
22
  <fieldset class="formbox">
23
23
  <legend class="counter" data-help="importFormats">${translate(
@@ -169,8 +169,13 @@ export default class Importer extends Utils.WithTemplate {
169
169
  this.onChange()
170
170
  }
171
171
 
172
+ set layer(layer) {
173
+ this._layer = layer
174
+ }
175
+
172
176
  get layer() {
173
177
  return (
178
+ this._layer ||
174
179
  this._umap.datalayers[this.layerId] ||
175
180
  this._umap.createDirtyDataLayer({ name: this.layerName })
176
181
  )
@@ -8,11 +8,21 @@ const PORTALS = [
8
8
  url: 'https://data.ampmetropole.fr',
9
9
  platform: 'opendatasoft',
10
10
  },
11
+ {
12
+ name: 'Auverge-Rhône-Alpes',
13
+ url: 'https://admin.open-datara.fr',
14
+ platform: 'prodige',
15
+ },
11
16
  {
12
17
  name: 'Bordeaux Métropole',
13
18
  url: 'https://opendata.bordeaux-metropole.fr',
14
19
  platform: 'opendatasoft',
15
20
  },
21
+ {
22
+ name: 'Nouvelle Aquitaine',
23
+ url: 'https://admin.sigena.fr',
24
+ platform: 'prodige',
25
+ },
16
26
  {
17
27
  name: 'Région Centre-Val de Loire',
18
28
  url: 'https://data.centrevaldeloire.fr',
@@ -33,6 +43,21 @@ const PORTALS = [
33
43
  url: 'https://data.iledefrance.fr',
34
44
  platform: 'opendatasoft',
35
45
  },
46
+ {
47
+ name: 'Martinique',
48
+ url: 'https://admin.geomartinique.fr',
49
+ platform: 'prodige',
50
+ },
51
+ {
52
+ name: 'Région Pays de la Loire',
53
+ url: 'https://admin.sigloire.fr',
54
+ platform: 'prodige',
55
+ },
56
+ {
57
+ name: 'Saint-Pierre et Miquelon',
58
+ url: 'https://admin.geospm.com',
59
+ platform: 'prodige',
60
+ },
36
61
  {
37
62
  name: 'Toulouse Métropole',
38
63
  url: 'https://data.toulouse-metropole.fr',
@@ -57,29 +82,54 @@ const TEMPLATE = `
57
82
  <option disabled selected value="">${translate('Choose a dataset')}</option>
58
83
  </select>
59
84
  <input type="hidden" name="geofield" data-ref="geofield">
60
- <label><input type="checkbox" name="in_bbox">${translate('Limit results to current map view')}</label>
85
+ <label data-ref="in_bbox" hidden><input type="checkbox" name="in_bbox">${translate('Limit results to current map view')}</label>
61
86
  </div>
62
87
  </div>
63
88
  `
64
89
 
65
- export class Importer {
66
- constructor(umap, options = {}) {
90
+ class Connector {
91
+ constructor(umap, baseUrl) {
67
92
  this.umap = umap
68
- this.name = options.name || 'Open Data'
69
- this.id = 'opendata'
70
- this.portals = options.choices || PORTALS
93
+ this.baseUrl = baseUrl
71
94
  }
95
+ }
72
96
 
73
- async fetchDatasets(baseUrl) {
97
+ class Prodige extends Connector {
98
+ async datasets() {
99
+ const datasets = []
100
+ const endpoint = this.umap.proxyUrl(
101
+ `${this.baseUrl}/api/ogc-features/collections.json`,
102
+ 3600
103
+ )
104
+ const response = await this.umap.request.get(endpoint)
105
+ if (!response?.ok) return datasets
106
+ const data = await response.json()
107
+ for (const dataset of data.collections) {
108
+ let url
109
+ for (const link of dataset.links) {
110
+ if (link.type === 'application/geo+json') url = link.href
111
+ }
112
+ if (!url) continue
113
+ datasets.push({
114
+ label: dataset.title,
115
+ url: this.umap.proxyUrl(url, 3600),
116
+ })
117
+ }
118
+ return datasets.sort((a, b) => Utils.naturalSort(a.label, b.label, U.lang))
119
+ }
120
+ }
121
+
122
+ class OpenDataSoft extends Connector {
123
+ async datasets() {
74
124
  let results = []
75
125
  let total = null
76
126
  const hardLimit = 500
77
127
  while (total === null || results.length < total) {
78
128
  const offset = results.length
79
129
  const response = await this.umap.request.get(
80
- `${baseUrl}/api/explore/v2.1/catalog/datasets?where=features%20in%20%28%22geo%22%29&limit=100&offset=${offset}&order_by=title asc`
130
+ `${this.baseUrl}/api/explore/v2.1/catalog/datasets?where=features%20in%20%28%22geo%22%29&limit=100&offset=${offset}&order_by=title asc`
81
131
  )
82
- if (!response.ok) break
132
+ if (!response?.ok) break
83
133
  const data = await response.json()
84
134
  if (total === null) {
85
135
  total = data.total_count
@@ -87,48 +137,85 @@ export class Importer {
87
137
  results = results.concat(data.results)
88
138
  if (total === null || results.length > hardLimit) break
89
139
  }
90
- return results
140
+ const datasets = []
141
+ for (const result of results) {
142
+ const fields = result.fields.filter((field) => field.type === 'geo_point_2d')
143
+ if (!fields.length) {
144
+ console.debug('No geofield found for', result)
145
+ continue
146
+ }
147
+ if (fields.length > 1) {
148
+ console.debug('More than one geofield found for', result)
149
+ }
150
+ const url = `${this.baseUrl}/api/explore/v2.1/catalog/datasets/${result.dataset_id}/exports/geojson?select=%2A&limit=-1&timezone=UTC&use_labels=false&epsg=4326`
151
+ const bbox_url = `${url}&where=in_bbox%28${fields[0].name}%2C%20{south},{west},{north},{east}%29`
152
+ datasets.push({
153
+ id: result.dataset_id,
154
+ label: `${result.metas.default.title} (${result.metas.default.records_count})`,
155
+ url,
156
+ bbox_url,
157
+ })
158
+ }
159
+ return datasets
160
+ }
161
+ }
162
+
163
+ export class Importer {
164
+ constructor(umap, options = {}) {
165
+ this.umap = umap
166
+ this.name = options.name || 'Open Data'
167
+ this.id = 'opendata'
168
+ this.portals = options.choices || PORTALS
169
+ }
170
+
171
+ resetSelect(select) {
172
+ Array.from(select.children).forEach((option) => {
173
+ if (!option.disabled) {
174
+ option.remove()
175
+ } else {
176
+ option.selected = true
177
+ }
178
+ })
91
179
  }
92
180
 
93
181
  async open(importer) {
94
- let fields_map = {}
95
- const [container, { portals, datasets, geofield }] =
182
+ const [container, { portals, datasets, in_bbox }] =
96
183
  Utils.loadTemplateWithRefs(TEMPLATE)
184
+ datasets.addEventListener('change', (event) => {
185
+ const select = event.target
186
+ const selected = select.options[select.selectedIndex]
187
+ const bbox_url = selected.dataset.bbox_url
188
+ in_bbox.checked = false
189
+ in_bbox.hidden = !bbox_url
190
+ })
97
191
  portals.addEventListener('change', async (event) => {
98
- const results = await this.fetchDatasets(event.target.value)
192
+ const select = event.target
193
+ const selected = select.options[select.selectedIndex]
194
+ const platform = selected.dataset.platform
195
+ let connector
196
+ if (platform === 'opendatasoft') {
197
+ connector = new OpenDataSoft(this.umap, selected.value)
198
+ } else if (platform === 'prodige') {
199
+ connector = new Prodige(this.umap, selected.value)
200
+ } else {
201
+ console.error('Unknown platform', platform)
202
+ return
203
+ }
204
+ const results = await connector.datasets(event.target.value)
99
205
  if (results) {
100
- fields_map = {}
101
- Array.from(datasets.children).forEach((option) => {
102
- if (!option.disabled) {
103
- option.remove()
104
- } else {
105
- option.selected = true
106
- }
107
- })
206
+ this.resetSelect(datasets)
108
207
  for (const result of results) {
109
- const fields = result.fields.filter((field) => field.type === 'geo_point_2d')
110
- if (!fields.length) {
111
- console.debug('No geofield found for', result)
112
- continue
113
- }
114
- if (fields.length > 1) {
115
- console.debug('More than one geofield found for', result)
116
- }
117
- fields_map[result.dataset_id] = fields[0].name
118
208
  const el = Utils.loadTemplate(
119
- `<option value="${result.dataset_id}">${result.metas.default.title} (${result.metas.default.records_count})</option>`
209
+ `<option value="${result.url}" data-url="${result.url}" data-bbox_url="${result.bbox_url || ''}">${result.label}</option>`
120
210
  )
121
211
  datasets.appendChild(el)
122
212
  }
123
213
  datasets.hidden = false
124
214
  }
125
215
  })
126
- datasets.addEventListener('change', (event) => {
127
- geofield.value = fields_map[event.target.value]
128
- })
129
216
  for (const instance of this.portals) {
130
217
  const el = Utils.loadTemplate(
131
- `<option value="${instance.url}">${instance.name}</option>`
218
+ `<option value="${instance.url}" data-platform="${instance.platform}">${instance.name}</option>`
132
219
  )
133
220
  portals.appendChild(el)
134
221
  }
@@ -138,9 +225,10 @@ export class Importer {
138
225
  Alert.error(translate('Please choose an instance first.'))
139
226
  return
140
227
  }
141
- let url = `${form.instance}/api/explore/v2.1/catalog/datasets/${form.dataset}/exports/geojson?select=%2A&limit=-1&timezone=UTC&use_labels=false&epsg=4326`
228
+ let url = form.dataset
142
229
  if (form.in_bbox) {
143
- url += `&where=in_bbox%28${form.geofield}%2C%20{south},{west},{north},{east}%29`
230
+ const selected = datasets.options[datasets.selectedIndex]
231
+ url = selected.dataset.bbox_url
144
232
  }
145
233
  importer.url = url
146
234
  importer.format = 'geojson'
@@ -0,0 +1,140 @@
1
+ import { uMapAlert as Alert } from '../../components/alerts/alert.js'
2
+ import { translate } from '../i18n.js'
3
+ import * as Utils from '../utils.js'
4
+ import { Form } from '../form/builder.js'
5
+
6
+ export const PROFILES = [
7
+ ['foot-walking', translate('Walking')],
8
+ ['foot-hiking', translate('Hiking')],
9
+ ['driving-car', translate('By car')],
10
+ ['cycling-regular', translate('Cycling')],
11
+ ['wheelchair', translate('Wheelchair')],
12
+ ]
13
+
14
+ export const PREFERENCES = [
15
+ ['recommended', translate('Recommended')],
16
+ ['fastest', translate('Fastest')],
17
+ ['shortest', translate('Shortest')],
18
+ ]
19
+
20
+ export class Importer {
21
+ constructor(umap) {
22
+ this.umap = umap
23
+ this.id = 'openrouteservice'
24
+ }
25
+
26
+ async loadORS() {
27
+ const mod = await import('../../../vendors/openrouteservice/ors-js-client.js')
28
+ return mod.default
29
+ }
30
+
31
+ async isochrone(latlng) {
32
+ const ORS = await this.loadORS()
33
+ const properties = {
34
+ range: 10,
35
+ lines: 1,
36
+ profile: 'foot-walking',
37
+ }
38
+
39
+ const metadatas = [
40
+ ['datalayer', { handler: 'DataLayerSwitcher', allowEmpty: true }],
41
+ [
42
+ 'profile',
43
+ { handler: 'Select', selectOptions: PROFILES, label: translate('Profile') },
44
+ ],
45
+ [
46
+ 'range',
47
+ {
48
+ handler: 'Range',
49
+ min: 5,
50
+ max: 60,
51
+ step: 1,
52
+ ticks: 11,
53
+ label: translate('Max time (in minutes)'),
54
+ },
55
+ ],
56
+ [
57
+ 'lines',
58
+ {
59
+ handler: 'Range',
60
+ min: 1,
61
+ max: 5,
62
+ step: 1,
63
+ label: translate('Number of lines'),
64
+ },
65
+ ],
66
+ ]
67
+ const form = new Form(properties, metadatas, { umap: this.umap })
68
+ // Needed for DataLayerSwitcher (which expects to be used with MutatingForm)
69
+ form._umap = this.umap
70
+
71
+ const Isochrones = new ORS.Isochrones({
72
+ api_key: this.umap.properties.ORSAPIKey,
73
+ })
74
+ this.umap.dialog.open({ template: form.build() }).then(async () => {
75
+ try {
76
+ const params = {
77
+ locations: [[latlng.lng, latlng.lat]],
78
+ profile: properties.profile,
79
+ range: [properties.range * 60],
80
+ }
81
+ if (properties.lines !== 1) {
82
+ params.interval = (properties.range / properties.lines) * 60
83
+ }
84
+ const data = await Isochrones.calculate(params)
85
+ this.umap.importer.build()
86
+ this.umap.importer.raw = JSON.stringify(data)
87
+ this.umap.importer.format = 'geojson'
88
+ this.umap.importer.layer = properties.datalayer
89
+ this.umap.importer.submit()
90
+ } catch (err) {
91
+ console.error(err)
92
+ }
93
+ })
94
+ }
95
+
96
+ async elevation(geometry) {
97
+ const ORS = await this.loadORS()
98
+
99
+ const Elevation = new ORS.Elevation({
100
+ api_key: this.umap.properties.ORSAPIKey,
101
+ })
102
+
103
+ try {
104
+ const data = await Elevation.lineElevation({
105
+ format_in: 'geojson',
106
+ format_out: 'geojson',
107
+ geometry: geometry,
108
+ })
109
+ return data?.geometry
110
+ } catch (err) {
111
+ console.debug(`An error occurred: ${err.status}`)
112
+ console.error(err)
113
+ }
114
+ }
115
+
116
+ async directions(properties) {
117
+ if (!properties?.coordinates || properties.coordinates.length < 2) {
118
+ console.error('Not enough coordinates to compute route', properties)
119
+ return
120
+ }
121
+ const ORS = await this.loadORS()
122
+ const Directions = new ORS.Directions({ api_key: this.umap.properties.ORSAPIKey })
123
+ try {
124
+ const featuresCollection = await Directions.calculate({
125
+ coordinates: properties.coordinates,
126
+ profile: properties.profile,
127
+ preference: properties.preference,
128
+ elevation: properties.elevation,
129
+ geometry_simplify: true,
130
+ instructions: false,
131
+ format: 'geojson',
132
+ })
133
+ const feature = featuresCollection.features[0]
134
+ return feature.geometry
135
+ } catch (err) {
136
+ console.debug(`An error occurred: ${err.status}`)
137
+ console.error(err)
138
+ }
139
+ }
140
+ }