]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/moderation_zone.js
Merge remote-tracking branch 'upstream/pull/7283'
[rails.git] / app / assets / javascripts / moderation_zone.js
1 //= require maplibre/map
2 //= require terra-draw/dist/terra-draw.umd
3 //= require terra-draw-maplibre-gl-adapter/dist/terra-draw-maplibre-gl-adapter.umd
4
5 /* globals terraDraw, terraDrawMaplibreGlAdapter */
6
7 $(function () {
8   const COORDINATES_FIELD_ID = "moderation_zone_zone";
9   const POSTGIS_LINE_POINTS_REGEXP = /[0-9-][ 0-9.,-]+/;
10   const SELECT_MODE_OPTIONS = {
11     flags: {
12       polygon: {
13         feature: {
14           draggable: false,
15           coordinates: {
16             midpoints: { draggable: true },
17             draggable: true,
18             snappable: true,
19             deletable: true,
20
21             // Disallow resizing of the geometry from a given origin.
22             resizable: false
23           }
24         }
25       }
26     }
27   };
28
29   const baseMap = new OSM.MapLibre.SecondaryMap();
30
31   baseMap.once("style.load", () => {
32     try {
33       const draw = createTerraDrawInstance(baseMap);
34       draw.on("finish", createTerraDrawFinishHandler(draw));
35       loadData(baseMap, draw);
36     } catch (e) {
37       // MapLibre is swallowing these exceptions silently, so I had
38       // to add this to know why my code was failing as I went.
39       console.error(e); // eslint-disable-line no-console
40       throw e;
41     }
42   });
43
44   function createTerraDrawInstance(map) {
45     return new terraDraw.TerraDraw({
46       adapter: new terraDrawMaplibreGlAdapter.TerraDrawMapLibreGLAdapter({
47         map,
48         lib: maplibregl
49       }),
50       modes: [
51         new terraDraw.TerraDrawPolygonMode(),
52         new terraDraw.TerraDrawSelectMode(SELECT_MODE_OPTIONS)
53       ]
54     });
55   }
56
57   function createTerraDrawFinishHandler(draw) {
58     return function (id, { mode, action }) {
59       if (mode === "polygon") {
60         draw.setMode("select");
61       } else if (mode === "select") {
62         // Nothing to do
63       } else {
64         throw new Error(`Unexpected mode "${mode}" (action: "${action}")`);
65       }
66
67       const feature = draw.getSnapshotFeature(id);
68       if (!feature) {
69         throw new Error(`Could not find feature with id ${id}`);
70       }
71
72       writeFormField(COORDINATES_FIELD_ID, feature);
73     };
74   }
75
76   function loadData(map, draw) {
77     const feature = readFormField(COORDINATES_FIELD_ID);
78     if (feature) {
79       startTerraDrawForEdit(draw, feature);
80       map.fitBounds(featureToBox(feature), { radius: 100 });
81     } else {
82       startTerraDrawForNew(draw);
83     }
84   }
85
86   function readFormField(fieldId) {
87     const target = document.getElementById(fieldId);
88     if (!target) {
89       throw new Error(`Could not find field #${fieldId}`);
90     }
91
92     const cleanValue = target.value.trim();
93     if (cleanValue === "") {
94       return null;
95     }
96
97     const match = POSTGIS_LINE_POINTS_REGEXP.exec(cleanValue);
98     if (!match) {
99       throw new Error(`Unexpected value in field #${fieldId}. Expected a WKT geometry, but found: ${target.value}`);
100     }
101
102     const coordinatesString = match[0];
103     const pointStrings = coordinatesString.split(",").map(s => s.trim());
104     const points = pointStrings.map(s => s.split(" ")).map(pairs => pairs.map(parseFloat));
105     return {
106       type: "Feature",
107       geometry: {
108         type: "Polygon",
109         coordinates: [points]
110       },
111       properties: {
112         mode: "polygon"
113       }
114     };
115   }
116
117   function startTerraDrawForEdit(draw, feature) {
118     draw.start();
119     draw.setMode("polygon");
120     const results = draw.addFeatures([feature]);
121     const invalidFeatures = [];
122     results.forEach(r => {
123       if (!r.valid) {
124         invalidFeatures.push(r);
125       }
126     });
127     if (invalidFeatures.length > 0) {
128       const invalidFeaturesString = invalidFeatures.map(JSON.stringify).join("\n");
129       throw new Error(`Failed to load features into TerraDraw:\n${invalidFeaturesString}`);
130     }
131     draw.setMode("select");
132   }
133
134   function startTerraDrawForNew(draw) {
135     draw.start();
136     draw.setMode("polygon");
137   }
138
139   function writeFormField(fieldId, feature) {
140     const coordinatesString = feature.geometry.coordinates[0]
141       .map(([lon, lat]) => `${lon} ${lat}`)
142       .join(",\n");
143     const target = document.getElementById(fieldId);
144     target.value = `POLYGON((\n${coordinatesString}\n))`;
145   }
146
147   function featureToBox(feature) {
148     const points = feature.geometry.coordinates[0];
149     const seed = new maplibregl.LngLatBounds(points[0], points[0]);
150     return points.reduce((bounds, point) => bounds.extend(point), seed);
151   }
152 });