Mercurial > gemma
comparison client/src/components/map/fairway/Profiles.vue @ 1372:553aadd97087
new cross profile workflow (WIP)
Needs fixing of some bugs and not so nice looks.
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Tue, 27 Nov 2018 12:59:26 +0100 |
parents | |
children | fa7d647f8d77 |
comparison
equal
deleted
inserted
replaced
1371:5b9b8eabcd01 | 1372:553aadd97087 |
---|---|
1 <template> | |
2 <div :class="['box ui-element rounded bg-white text-nowrap', { expanded: showProfiles }]"> | |
3 <div style="width: 20rem"> | |
4 <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center"> | |
5 <font-awesome-icon icon="chart-area" class="mr-2"></font-awesome-icon> | |
6 Profiles | |
7 <font-awesome-icon | |
8 icon="times" | |
9 class="ml-auto text-muted" | |
10 @click="$store.commit('application/showProfiles', false)" | |
11 ></font-awesome-icon> | |
12 </h6> | |
13 <div class="d-flex flex-column p-3 flex-grow-1 text-left position-relative"> | |
14 <div class="loading d-flex justify-content-center align-items-center" v-if="surveysLoading || profileLoading"> | |
15 <font-awesome-icon icon="spinner" spin /> | |
16 </div> | |
17 <small class="text-muted">Bottleneck:</small> | |
18 <select @click="moveToBottleneck" v-model="selectedBottleneck" class="form-control form-control-sm"> | |
19 <option :value="null">Select Bottleneck</option> | |
20 <option | |
21 v-for="bn in bottlenecks" | |
22 :key="bn.properties.name" | |
23 :value="bn.properties.name" | |
24 >{{ bn.properties.name }}</option> | |
25 </select> | |
26 <div v-if="selectedBottleneck"> | |
27 <div class="d-flex mt-2"> | |
28 <div class="flex-fill"> | |
29 <small class="text-muted">Sounding Result:</small> | |
30 <select v-model="selectedSurvey" class="form-control form-control-sm"> | |
31 <option | |
32 v-for="survey in surveys" | |
33 :key="survey.date_info" | |
34 :value="survey" | |
35 >{{ survey.date_info }}</option> | |
36 </select> | |
37 </div> | |
38 <div class="flex-fill ml-3" v-if="selectedSurvey && surveys.length > 1"> | |
39 <small class="text-muted mt-1">Compare with:</small> | |
40 <select v-model="additionalSurvey" class="form-control form-control-sm"> | |
41 <option :value="null">None</option> | |
42 <option | |
43 v-for="survey in additionalSurveys" | |
44 :key="survey.date_info" | |
45 :value="survey" | |
46 >{{ survey.date_info }}</option> | |
47 </select> | |
48 </div> | |
49 </div> | |
50 <hr class="w-100"> | |
51 <small class="d-flex text-left my-2" v-if="startPoint && endPoint"> | |
52 <div class="text-nowrap mr-3"> | |
53 <b>Start:</b> | |
54 <br> | |
55 Lat: {{ startPoint[1] }} | |
56 <br> | |
57 Lon: {{ startPoint[0] }} | |
58 </div> | |
59 <div class="text-nowrap"> | |
60 <b>End:</b> | |
61 <br> | |
62 Lat: {{ endPoint[1] }} | |
63 <br> | |
64 Lon: {{ endPoint[0] }} | |
65 </div> | |
66 <button v-clipboard:copy="coordinatesForClipboard" | |
67 v-clipboard:success="onCopyCoordinates" | |
68 class="btn btn-info btn-sm ml-auto mt-auto"> | |
69 <font-awesome-icon icon="copy" /> | |
70 </button> | |
71 </small> | |
72 <div class="d-flex"> | |
73 <div class="pr-3 w-50" v-if="startPoint && endPoint"> | |
74 <button class="btn btn-info btn-sm w-100" | |
75 @click="showLabelInput = !showLabelInput"> | |
76 <font-awesome-icon :icon="showLabelInput ? 'times' : 'check'" /> | |
77 {{ showLabelInput ? "Cancel" : "Save" }} | |
78 </button> | |
79 </div> | |
80 <div :class="startPoint && endPoint ? 'w-50' : 'w-100'"> | |
81 <button | |
82 class="btn btn-info btn-sm w-100" | |
83 @click="toggleCutTool" | |
84 > | |
85 <font-awesome-icon :icon="cutTool && cutTool.getActive() ? 'times' : 'plus'"></font-awesome-icon> | |
86 {{ cutTool && cutTool.getActive() ? "Cancel" : "New" }} | |
87 </button> | |
88 </div> | |
89 </div> | |
90 <div v-if="showLabelInput" class="mt-2"> | |
91 <small class="text-muted">Enter label for cross profile:</small> | |
92 <div class="position-relative"> | |
93 <input class="form-control form-control-sm pr-5" v-model="cutLabel" /> | |
94 <button class="btn btn-sm btn-info position-absolute input-button-right" | |
95 @click="saveCut" | |
96 v-if="cutLabel" | |
97 style="top: 0; right: 0;"> | |
98 <font-awesome-icon icon="check" /> | |
99 </button> | |
100 </div> | |
101 </div> | |
102 <small class="text-muted d-block mt-2">Saved cross profiles:</small> | |
103 <select class="form-control form-control-sm" v-model="coordinatesSelect"> | |
104 <option></option> | |
105 <option v-for="(cut, index) in previousCuts" :value="cut.coordinates" :key="index"> | |
106 {{ cut.label }} | |
107 </option> | |
108 </select> | |
109 <small class="text-muted d-block mt-2">Enter coordinates manually:</small> | |
110 <div class="position-relative"> | |
111 <input class="form-control form-control-sm pr-5" placeholder="Lat,Lon,Lat,Lon" v-model="coordinatesInput" /> | |
112 <button class="btn btn-sm btn-info position-absolute input-button-right" | |
113 @click="applyManualCoordinates" | |
114 style="top: 0; right: 0;" | |
115 v-if="coordinatesInputIsValid"> | |
116 <font-awesome-icon icon="check" /> | |
117 </button> | |
118 </div> | |
119 </div> | |
120 </div> | |
121 </div> | |
122 </div> | |
123 </template> | |
124 | |
125 <style lang="sass" scoped> | |
126 .loading | |
127 background: rgba(255, 255, 255, 0.96) | |
128 position: absolute | |
129 z-index: 99 | |
130 top: 0 | |
131 right: 0 | |
132 bottom: 0 | |
133 left: 0 | |
134 | |
135 .input-button-right | |
136 border-top-right-radius: $border-radius | |
137 border-bottom-right-radius: $border-radius | |
138 border-top-left-radius: 0 !important | |
139 border-bottom-left-radius: 0 !important | |
140 </style> | |
141 | |
142 <script> | |
143 /* This is Free Software under GNU Affero General Public License v >= 3.0 | |
144 * without warranty, see README.md and license for details. | |
145 * | |
146 * SPDX-License-Identifier: AGPL-3.0-or-later | |
147 * License-Filename: LICENSES/AGPL-3.0.txt | |
148 * | |
149 * Copyright (C) 2018 by via donau | |
150 * – Österreichische Wasserstraßen-Gesellschaft mbH | |
151 * Software engineering by Intevation GmbH | |
152 * | |
153 * Author(s): | |
154 * Markus Kottländer <markus.kottlaender@intevation.de> | |
155 */ | |
156 import { mapState, mapGetters } from "vuex"; | |
157 import Feature from "ol/Feature"; | |
158 import LineString from "ol/geom/LineString"; | |
159 import { displayError, displayInfo } from "../../../lib/errors.js"; | |
160 | |
161 export default { | |
162 name: "profiles", | |
163 data() { | |
164 return { | |
165 coordinatesInput: "", | |
166 coordinatesSelect: null, | |
167 cutLabel: "", | |
168 showLabelInput: false | |
169 }; | |
170 }, | |
171 computed: { | |
172 ...mapGetters("map", ["getVSourceByName"]), | |
173 ...mapState("application", ["showProfiles"]), | |
174 ...mapState("map", ["lineTool", "polygonTool", "cutTool"]), | |
175 ...mapState("bottlenecks", ["bottlenecks", "surveys", "surveysLoading"]), | |
176 ...mapState("fairwayprofile", [ | |
177 "previousCuts", | |
178 "startPoint", | |
179 "endPoint", | |
180 "profileLoading" | |
181 ]), | |
182 selectedBottleneck: { | |
183 get() { | |
184 return this.$store.state.bottlenecks.selectedBottleneck; | |
185 }, | |
186 set(name) { | |
187 this.loading = true; | |
188 this.$store.dispatch("bottlenecks/setSelectedBottleneck", name); | |
189 } | |
190 }, | |
191 selectedSurvey: { | |
192 get() { | |
193 return this.$store.state.bottlenecks.selectedSurvey; | |
194 }, | |
195 set(survey) { | |
196 this.$store.commit("bottlenecks/setSelectedSurvey", survey); | |
197 } | |
198 }, | |
199 additionalSurvey: { | |
200 get() { | |
201 return this.$store.state.fairwayprofile.additionalSurvey; | |
202 }, | |
203 set(survey) { | |
204 this.$store.commit("fairwayprofile/setAdditionalSurvey", survey); | |
205 } | |
206 }, | |
207 additionalSurveys() { | |
208 return this.surveys.filter(survey => survey !== this.selectedSurvey); | |
209 }, | |
210 coordinatesForClipboard() { | |
211 return ( | |
212 this.startPoint[1] + | |
213 "," + | |
214 this.startPoint[0] + | |
215 "," + | |
216 this.endPoint[1] + | |
217 "," + | |
218 this.endPoint[0] | |
219 ); | |
220 }, | |
221 coordinatesInputIsValid() { | |
222 const coordinates = this.coordinatesInput | |
223 .split(",") | |
224 .map(coord => parseFloat(coord.trim())) | |
225 .filter(c => Number(c) === c); | |
226 return coordinates.length === 4; | |
227 } | |
228 }, | |
229 watch: { | |
230 selectedBottleneck() { | |
231 this.$store.dispatch("fairwayprofile/previousCuts"); | |
232 this.cutLabel = | |
233 this.selectedBottleneck + " (" + new Date().toISOString() + ")"; | |
234 }, | |
235 selectedSurvey(survey) { | |
236 if (survey) this.$store.dispatch("fairwayprofile/loadProfile", survey); | |
237 }, | |
238 additionalSurvey(survey) { | |
239 if (survey) this.$store.dispatch("fairwayprofile/loadProfile", survey); | |
240 }, | |
241 coordinatesSelect(newValue) { | |
242 if (newValue) { | |
243 this.applyCoordinates(newValue); | |
244 } | |
245 } | |
246 }, | |
247 methods: { | |
248 toggleCutTool() { | |
249 if (this.selectedSurvey) { | |
250 this.cutTool.setActive(!this.cutTool.getActive()); | |
251 this.lineTool.setActive(false); | |
252 this.polygonTool.setActive(false); | |
253 this.$store.commit("map/setCurrentMeasurement", null); | |
254 } | |
255 }, | |
256 onCopyCoordinates() { | |
257 displayInfo({ | |
258 title: "Success", | |
259 message: "Coordinates copied to clipboard!" | |
260 }); | |
261 }, | |
262 applyManualCoordinates() { | |
263 const coordinates = this.coordinatesInput | |
264 .split(",") | |
265 .map(coord => parseFloat(coord.trim())); | |
266 this.coordinatesSelect = null; | |
267 this.coordinatesInput = ""; | |
268 this.applyCoordinates([ | |
269 coordinates[1], | |
270 coordinates[0], | |
271 coordinates[3], | |
272 coordinates[2] | |
273 ]); | |
274 }, | |
275 applyCoordinates(coordinates) { | |
276 // allow only numbers | |
277 coordinates = coordinates.filter(c => Number(c) === c); | |
278 if (coordinates.length === 4) { | |
279 // draw line on map | |
280 this.getVSourceByName("Cut Tool").clear(); | |
281 const cut = new Feature({ | |
282 geometry: new LineString([ | |
283 [coordinates[0], coordinates[1]], | |
284 [coordinates[2], coordinates[3]] | |
285 ]).transform("EPSG:4326", "EPSG:3857") | |
286 }); | |
287 this.getVSourceByName("Cut Tool").addFeature(cut); | |
288 | |
289 // draw diagram | |
290 this.$store.dispatch("fairwayprofile/cut", cut); | |
291 } else { | |
292 displayError({ | |
293 title: "Invalid input", | |
294 message: | |
295 "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon" | |
296 }); | |
297 } | |
298 }, | |
299 saveCut() { | |
300 const previousCuts = | |
301 JSON.parse(localStorage.getItem("previousCuts")) || []; | |
302 const newEntry = { | |
303 label: this.cutLabel, | |
304 bottleneckName: this.selectedBottleneck, | |
305 coordinates: [...this.startPoint, ...this.endPoint] | |
306 }; | |
307 const existingEntry = previousCuts.find(cut => { | |
308 return JSON.stringify(cut) === JSON.stringify(newEntry); | |
309 }); | |
310 if (!existingEntry) previousCuts.push(newEntry); | |
311 if (previousCuts.length > 100) previousCuts.shift(); | |
312 localStorage.setItem("previousCuts", JSON.stringify(previousCuts)); | |
313 this.$store.dispatch("fairwayprofile/previousCuts"); | |
314 | |
315 this.showLabelInput = false; | |
316 this.cutLabel = ""; | |
317 displayInfo({ | |
318 title: "Coordinates saved!", | |
319 message: | |
320 'You can now select these coordinates from the "Saved cross profiles" menu to restore this cross profile.' | |
321 }); | |
322 }, | |
323 moveToBottleneck() { | |
324 const bottleneck = this.bottlenecks.find( | |
325 bn => bn.properties.name === this.selectedBottleneck | |
326 ); | |
327 if (!bottleneck) return; | |
328 this.$store.commit("map/moveMap", { | |
329 coordinates: bottleneck.geometry.coordinates, | |
330 zoom: 17, | |
331 preventZoomOut: true | |
332 }); | |
333 } | |
334 } | |
335 }; | |
336 </script> |