umap-project 3.1.2__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 (199) 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 +22 -18
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +21 -17
  6. umap/management/commands/export_pictogram.py +29 -0
  7. umap/management/commands/migrate_to_S3.py +5 -1
  8. umap/management/commands/purge_old_versions.py +8 -6
  9. umap/settings/__init__.py +21 -0
  10. umap/settings/base.py +3 -0
  11. umap/static/umap/content.css +7 -2
  12. umap/static/umap/css/contextmenu.css +58 -2
  13. umap/static/umap/css/form.css +175 -45
  14. umap/static/umap/css/icon.css +97 -3
  15. umap/static/umap/css/panel.css +31 -1
  16. umap/static/umap/img/16-white.svg +21 -40
  17. umap/static/umap/img/16.svg +1 -1
  18. umap/static/umap/img/24-white.svg +9 -9
  19. umap/static/umap/img/24.svg +23 -10
  20. umap/static/umap/img/source/16-white.svg +23 -41
  21. umap/static/umap/img/source/16.svg +1 -1
  22. umap/static/umap/img/source/24-white.svg +11 -11
  23. umap/static/umap/img/source/24.svg +25 -12
  24. umap/static/umap/js/modules/browser.js +1 -1
  25. umap/static/umap/js/modules/caption.js +8 -0
  26. umap/static/umap/js/modules/data/features.js +331 -202
  27. umap/static/umap/js/modules/data/layer.js +263 -152
  28. umap/static/umap/js/modules/facets.js +2 -2
  29. umap/static/umap/js/modules/form/builder.js +11 -7
  30. umap/static/umap/js/modules/form/fields.js +66 -26
  31. umap/static/umap/js/modules/formatter.js +78 -28
  32. umap/static/umap/js/modules/importer.js +6 -1
  33. umap/static/umap/js/modules/importers/opendata.js +138 -33
  34. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  35. umap/static/umap/js/modules/managers.js +67 -0
  36. umap/static/umap/js/modules/printer.js +107 -0
  37. umap/static/umap/js/modules/rendering/controls.js +78 -2
  38. umap/static/umap/js/modules/rendering/icon.js +116 -87
  39. umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
  40. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  41. umap/static/umap/js/modules/rendering/map.js +6 -2
  42. umap/static/umap/js/modules/rendering/template.js +71 -1
  43. umap/static/umap/js/modules/rendering/ui.js +111 -34
  44. umap/static/umap/js/modules/rules.js +76 -23
  45. umap/static/umap/js/modules/schema.js +27 -0
  46. umap/static/umap/js/modules/share.js +19 -12
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/updaters.js +1 -6
  49. umap/static/umap/js/modules/tableeditor.js +13 -37
  50. umap/static/umap/js/modules/templates.js +7 -6
  51. umap/static/umap/js/modules/ui/bar.js +6 -1
  52. umap/static/umap/js/modules/ui/base.js +24 -9
  53. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  54. umap/static/umap/js/modules/ui/dialog.js +7 -4
  55. umap/static/umap/js/modules/ui/panel.js +7 -0
  56. umap/static/umap/js/modules/umap.js +84 -67
  57. umap/static/umap/js/modules/utils.js +8 -7
  58. umap/static/umap/js/umap.controls.js +22 -57
  59. umap/static/umap/locale/am_ET.js +81 -9
  60. umap/static/umap/locale/am_ET.json +81 -9
  61. umap/static/umap/locale/ar.js +81 -9
  62. umap/static/umap/locale/ar.json +81 -9
  63. umap/static/umap/locale/ast.js +81 -9
  64. umap/static/umap/locale/ast.json +81 -9
  65. umap/static/umap/locale/bg.js +81 -9
  66. umap/static/umap/locale/bg.json +81 -9
  67. umap/static/umap/locale/br.js +68 -29
  68. umap/static/umap/locale/br.json +68 -29
  69. umap/static/umap/locale/ca.js +88 -16
  70. umap/static/umap/locale/ca.json +88 -16
  71. umap/static/umap/locale/cs_CZ.js +81 -9
  72. umap/static/umap/locale/cs_CZ.json +81 -9
  73. umap/static/umap/locale/da.js +48 -9
  74. umap/static/umap/locale/da.json +48 -9
  75. umap/static/umap/locale/de.js +48 -9
  76. umap/static/umap/locale/de.json +48 -9
  77. umap/static/umap/locale/el.js +58 -13
  78. umap/static/umap/locale/el.json +58 -13
  79. umap/static/umap/locale/en.js +48 -9
  80. umap/static/umap/locale/en.json +48 -9
  81. umap/static/umap/locale/en_US.json +81 -9
  82. umap/static/umap/locale/es.js +48 -9
  83. umap/static/umap/locale/es.json +48 -9
  84. umap/static/umap/locale/et.js +81 -9
  85. umap/static/umap/locale/et.json +81 -9
  86. umap/static/umap/locale/eu.js +97 -25
  87. umap/static/umap/locale/eu.json +97 -25
  88. umap/static/umap/locale/fa_IR.js +81 -9
  89. umap/static/umap/locale/fa_IR.json +81 -9
  90. umap/static/umap/locale/fi.js +81 -9
  91. umap/static/umap/locale/fi.json +81 -9
  92. umap/static/umap/locale/fr.js +48 -9
  93. umap/static/umap/locale/fr.json +48 -9
  94. umap/static/umap/locale/gl.js +81 -9
  95. umap/static/umap/locale/gl.json +81 -9
  96. umap/static/umap/locale/he.js +81 -9
  97. umap/static/umap/locale/he.json +81 -9
  98. umap/static/umap/locale/hr.js +81 -9
  99. umap/static/umap/locale/hr.json +81 -9
  100. umap/static/umap/locale/hu.js +72 -27
  101. umap/static/umap/locale/hu.json +72 -27
  102. umap/static/umap/locale/id.js +81 -9
  103. umap/static/umap/locale/id.json +81 -9
  104. umap/static/umap/locale/is.js +81 -9
  105. umap/static/umap/locale/is.json +81 -9
  106. umap/static/umap/locale/it.js +48 -9
  107. umap/static/umap/locale/it.json +48 -9
  108. umap/static/umap/locale/ja.js +81 -9
  109. umap/static/umap/locale/ja.json +81 -9
  110. umap/static/umap/locale/ko.js +81 -9
  111. umap/static/umap/locale/ko.json +81 -9
  112. umap/static/umap/locale/lt.js +81 -9
  113. umap/static/umap/locale/lt.json +81 -9
  114. umap/static/umap/locale/ms.js +81 -9
  115. umap/static/umap/locale/ms.json +81 -9
  116. umap/static/umap/locale/nl.js +48 -9
  117. umap/static/umap/locale/nl.json +48 -9
  118. umap/static/umap/locale/no.js +81 -9
  119. umap/static/umap/locale/no.json +81 -9
  120. umap/static/umap/locale/pl.js +81 -9
  121. umap/static/umap/locale/pl.json +81 -9
  122. umap/static/umap/locale/pl_PL.json +81 -9
  123. umap/static/umap/locale/pt.js +81 -9
  124. umap/static/umap/locale/pt.json +81 -9
  125. umap/static/umap/locale/pt_BR.js +91 -19
  126. umap/static/umap/locale/pt_BR.json +91 -19
  127. umap/static/umap/locale/pt_PT.js +81 -9
  128. umap/static/umap/locale/pt_PT.json +81 -9
  129. umap/static/umap/locale/ro.js +81 -9
  130. umap/static/umap/locale/ro.json +81 -9
  131. umap/static/umap/locale/ru.js +81 -9
  132. umap/static/umap/locale/ru.json +81 -9
  133. umap/static/umap/locale/sk_SK.js +81 -9
  134. umap/static/umap/locale/sk_SK.json +81 -9
  135. umap/static/umap/locale/sl.js +81 -9
  136. umap/static/umap/locale/sl.json +81 -9
  137. umap/static/umap/locale/sr.js +81 -9
  138. umap/static/umap/locale/sr.json +81 -9
  139. umap/static/umap/locale/sv.js +81 -9
  140. umap/static/umap/locale/sv.json +81 -9
  141. umap/static/umap/locale/th_TH.js +81 -9
  142. umap/static/umap/locale/th_TH.json +81 -9
  143. umap/static/umap/locale/tr.js +81 -9
  144. umap/static/umap/locale/tr.json +81 -9
  145. umap/static/umap/locale/uk_UA.js +81 -9
  146. umap/static/umap/locale/uk_UA.json +81 -9
  147. umap/static/umap/locale/vi.js +81 -9
  148. umap/static/umap/locale/vi.json +81 -9
  149. umap/static/umap/locale/vi_VN.json +81 -9
  150. umap/static/umap/locale/zh.js +81 -9
  151. umap/static/umap/locale/zh.json +81 -9
  152. umap/static/umap/locale/zh_CN.json +81 -9
  153. umap/static/umap/locale/zh_TW.Big5.json +81 -9
  154. umap/static/umap/locale/zh_TW.js +98 -26
  155. umap/static/umap/locale/zh_TW.json +98 -26
  156. umap/static/umap/map.css +325 -102
  157. umap/static/umap/vars.css +1 -0
  158. umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
  159. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  160. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  161. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  162. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  163. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  164. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  165. umap/storage/fs.py +3 -2
  166. umap/storage/staticfiles.py +12 -0
  167. umap/templates/base.html +4 -1
  168. umap/templates/umap/css.html +0 -4
  169. umap/templates/umap/js.html +1 -3
  170. umap/tests/base.py +9 -1
  171. umap/tests/integration/test_basics.py +3 -1
  172. umap/tests/integration/test_conditional_rules.py +79 -37
  173. umap/tests/integration/test_datalayer.py +1 -1
  174. umap/tests/integration/test_draw_polygon.py +3 -5
  175. umap/tests/integration/test_draw_polyline.py +4 -6
  176. umap/tests/integration/test_draw_route.py +178 -0
  177. umap/tests/integration/test_edit_datalayer.py +1 -1
  178. umap/tests/integration/test_edit_map.py +1 -1
  179. umap/tests/integration/test_edit_marker.py +8 -8
  180. umap/tests/integration/test_edit_polygon.py +2 -2
  181. umap/tests/integration/test_export_map.py +84 -10
  182. umap/tests/integration/test_import.py +140 -0
  183. umap/tests/integration/test_map_preview.py +1 -1
  184. umap/tests/integration/test_optimistic_merge.py +72 -12
  185. umap/tests/integration/test_share.py +1 -1
  186. umap/tests/integration/test_tableeditor.py +10 -7
  187. umap/tests/integration/test_websocket_sync.py +4 -4
  188. umap/utils.py +37 -0
  189. umap/views.py +18 -2
  190. umap_project-3.3.0.dist-info/METADATA +76 -0
  191. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/RECORD +194 -188
  192. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  193. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  194. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  195. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  196. umap_project-3.1.2.dist-info/METADATA +0 -68
  197. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  198. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
+ }
@@ -1,3 +1,5 @@
1
+ import * as Utils from './utils.js'
2
+
1
3
  export class DataLayerManager extends Object {
2
4
  add(datalayer) {
3
5
  this[datalayer.id] = datalayer
@@ -43,4 +45,69 @@ export class DataLayerManager extends Object {
43
45
  if (!next.canBrowse()) return this.next(next)
44
46
  return next
45
47
  }
48
+ first() {
49
+ return this.active()[0]
50
+ }
51
+ last() {
52
+ const layers = this.active()
53
+ return layers[layers.length - 1]
54
+ }
55
+ }
56
+
57
+ export class FeatureManager extends Map {
58
+ add(feature) {
59
+ if (this.has(feature.id)) {
60
+ console.error('Duplicate id', feature, this.get(feature.id))
61
+ feature.id = Utils.generateId()
62
+ }
63
+ this.set(feature.id, feature)
64
+ }
65
+
66
+ all() {
67
+ return Array.from(this.values())
68
+ }
69
+
70
+ visible() {
71
+ return this.all().filter((feature) => !feature.isFiltered())
72
+ }
73
+
74
+ del(feature) {
75
+ this.delete(feature.id)
76
+ }
77
+
78
+ count() {
79
+ return this.size
80
+ }
81
+
82
+ sort(by) {
83
+ const features = this.all()
84
+ Utils.sortFeatures(features, by, U.lang)
85
+ this.clear()
86
+ for (const feature of features) {
87
+ this.set(feature.id, feature)
88
+ }
89
+ }
90
+
91
+ getIndex(feature) {
92
+ const entries = Array.from(this)
93
+ return entries.findIndex(([id]) => id === feature.id)
94
+ }
95
+
96
+ first() {
97
+ return this.values().next().value
98
+ }
99
+
100
+ last() {
101
+ return this.all()[this.size - 1]
102
+ }
103
+
104
+ next(feature) {
105
+ const index = this.getIndex(feature)
106
+ return this.all()[index + 1]
107
+ }
108
+
109
+ prev(feature) {
110
+ const index = this.getIndex(feature)
111
+ return this.all()[index - 1]
112
+ }
46
113
  }
@@ -0,0 +1,107 @@
1
+ import { translate } from './i18n.js'
2
+ import * as Utils from './utils.js'
3
+
4
+ export default class Printer {
5
+ constructor(umap) {
6
+ this.umap = umap
7
+ this.dialog = this.umap.dialog
8
+ }
9
+
10
+ build() {
11
+ const [container, { format, mode, scale }] = Utils.loadTemplateWithRefs(`
12
+ <div>
13
+ <h3>${translate('Print map')}</h3>
14
+ <div class="formbox">
15
+ <label>${translate('Choose a format')}
16
+ <select name="format" data-ref="format">
17
+ <option value="a4">A4</option>
18
+ <option value="usletter">US Letter</option>
19
+ </select>
20
+ </label>
21
+ <label>${translate('Scale map')}
22
+ <input type="range" min="50" max="150" name="scale" data-ref="scale" />
23
+ </label>
24
+ <div class="umap-multiplechoice by2" data-ref="mode">
25
+ <input type="radio" name="mode" id="mode.0" value="portrait"><label for="mode.0">${translate('portrait')}</label>
26
+ <input type="radio" name="mode" id="mode.1" value="landscape" checked=""><label for="mode.1">${translate('landscape')}</label>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ `)
31
+ this.container = container
32
+ format.addEventListener('change', () => this.resizeMap())
33
+ mode.addEventListener('change', () => this.resizeMap())
34
+ scale.addEventListener('change', () => this.resizeMap())
35
+ }
36
+
37
+ resetSize() {
38
+ const map = this.umap._leafletMap
39
+ for (const name of Array.from(map._container.classList)) {
40
+ if (name.startsWith('print-')) {
41
+ map._container.classList.remove(name)
42
+ }
43
+ }
44
+ map._container.removeAttribute('style')
45
+ map.invalidateSize()
46
+ }
47
+
48
+ resizeMap() {
49
+ const form = this.dialog.collectFormData()
50
+ this.resetSize()
51
+ const map = this.umap._leafletMap
52
+ if (form.format && form.mode) {
53
+ map._container.classList.add(`print-${form.format}`)
54
+ map._container.classList.add(`print-${form.mode}`)
55
+ map._container.style.width = `${form.scale}%`
56
+ map.invalidateSize()
57
+ }
58
+ }
59
+
60
+ open(action = 'print') {
61
+ if (!this.container) this.build()
62
+ this.action = action
63
+ const acceptLabel = action === 'print' ? translate('Print') : translate('Download')
64
+ this.dialog.on(
65
+ 'close',
66
+ (event) => {
67
+ if (event.target.returnValue !== 'accept') {
68
+ this.resetSize()
69
+ }
70
+ },
71
+ { once: true }
72
+ )
73
+ this.dialog
74
+ .open({ template: this.container, cancel: false, accept: acceptLabel })
75
+ .then((form) => this.onSubmit(form))
76
+ this.resizeMap()
77
+ }
78
+
79
+ async onSubmit(form) {
80
+ this.umap.fire('dataloading', { id: 'screenshot' })
81
+ if (this.action === 'print') {
82
+ const win = window.open('', '_blank')
83
+ // Using document.body.appendChild here will end with black font
84
+ // on a black blackground, no idea why.
85
+ win.document.write(`<span>${translate('Preparing the print…')}</span>`)
86
+ // screenshot must be called after window.open, no idea why,
87
+ // otherwise window.open sometimes fails and returns null.
88
+ const screenshot = await this.umap.screenshot()
89
+ const img = await screenshot.toPng()
90
+ img.addEventListener('load', () => {
91
+ win.print()
92
+ win.close()
93
+ })
94
+ win.document.querySelector('span').remove()
95
+ win.document.body.appendChild(img)
96
+ win.focus()
97
+ } else {
98
+ const screenshot = await this.umap.screenshot()
99
+ await screenshot.download({
100
+ format: this.action,
101
+ filename: Utils.slugify(this.umap.properties.name),
102
+ })
103
+ }
104
+ this.resetSize()
105
+ this.umap.fire('dataload', { id: 'screenshot' })
106
+ }
107
+ }
@@ -138,11 +138,21 @@ const BaseButton = Control.extend({
138
138
  button.addEventListener('dblclick', (event) => {
139
139
  event.stopPropagation()
140
140
  })
141
- this.afterAdd(container)
141
+ if (this.options.icon) {
142
+ button.appendChild(
143
+ Utils.loadTemplate(`<i class="icon icon-24 ${this.options.icon}"></i>`)
144
+ )
145
+ }
146
+ this.afterAdd(container, map)
142
147
  return container
143
148
  },
144
149
 
145
- afterAdd: (container) => {},
150
+ onRemove(map) {
151
+ this.afterRemove(map)
152
+ },
153
+
154
+ afterAdd: (container, map) => {},
155
+ afterRemove: (map) => {},
146
156
  })
147
157
 
148
158
  export const DataLayersControl = BaseButton.extend({
@@ -150,6 +160,7 @@ export const DataLayersControl = BaseButton.extend({
150
160
  position: 'topleft',
151
161
  className: 'umap-control-browse',
152
162
  title: translate('Open browser'),
163
+ icon: 'icon-layers',
153
164
  },
154
165
 
155
166
  afterAdd: function (container) {
@@ -166,6 +177,7 @@ export const CaptionControl = BaseButton.extend({
166
177
  position: 'topleft',
167
178
  className: 'umap-control-caption',
168
179
  title: translate('About'),
180
+ icon: 'icon-caption',
169
181
  },
170
182
 
171
183
  onClick: function () {
@@ -178,6 +190,7 @@ export const EmbedControl = BaseButton.extend({
178
190
  position: 'topleft',
179
191
  title: translate('Share and download'),
180
192
  className: 'leaflet-control-embed',
193
+ icon: 'icon-share',
181
194
  },
182
195
 
183
196
  onClick: function () {
@@ -185,6 +198,69 @@ export const EmbedControl = BaseButton.extend({
185
198
  },
186
199
  })
187
200
 
201
+ export const PrintControl = BaseButton.extend({
202
+ options: {
203
+ position: 'topleft',
204
+ title: translate('Print'),
205
+ icon: 'icon-print',
206
+ },
207
+
208
+ onClick: function () {
209
+ this._umap.openPrinter('print')
210
+ },
211
+ })
212
+
213
+ export const SearchControl = BaseButton.extend({
214
+ options: {
215
+ position: 'topleft',
216
+ title: translate('Search location'),
217
+ className: 'leaflet-control-search',
218
+ icon: 'icon-search',
219
+ },
220
+
221
+ afterAdd(container, map) {
222
+ this.layer = L.layerGroup().addTo(map)
223
+ this.photonOptions = {
224
+ limit: 10,
225
+ noResultLabel: translate('No results'),
226
+ }
227
+ if (map.options.photonUrl) this.photonOptions.url = map.options.photonUrl
228
+ },
229
+
230
+ afterRemove: function (map) {
231
+ this.layer.remove()
232
+ },
233
+
234
+ onClick: function () {
235
+ const template = `
236
+ <div>
237
+ <h3><i class="icon icon-16 icon-search"></i>${translate('Search location')}</h3>
238
+ <input class="photon-input" data-ref=input />
239
+ <div class="photon-autocomplete" data-ref=resultsContainer></div>
240
+ </div>
241
+ `
242
+ const [container, { input, resultsContainer }] =
243
+ Utils.loadTemplateWithRefs(template)
244
+ const id = Math.random()
245
+ this._umap.panel.open({ content: container }).then(() => {
246
+ this.search = new U.Search(
247
+ this._umap._leafletMap,
248
+ input,
249
+ this.layer,
250
+ this.photonOptions
251
+ )
252
+ this.search.on('ajax:send', () => {
253
+ this._umap.fire('dataloading', { id: id })
254
+ })
255
+ this.search.on('ajax:return', () => {
256
+ this._umap.fire('dataload', { id: id })
257
+ })
258
+ this.search.resultsContainer = resultsContainer
259
+ input.focus()
260
+ })
261
+ },
262
+ })
263
+
188
264
  export const AttributionControl = Control.Attribution.extend({
189
265
  options: {
190
266
  prefix: '',