Parent Directory | Revision Log
Prepared 64 bit id support
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of OSM2Go. |
5 | * |
6 | * OSM2Go is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * OSM2Go is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "appdata.h" |
21 | |
22 | /* --------------------- misc local helper functions ---------------- */ |
23 | |
24 | static void transfer_relations(osm_t *osm, way_t *dst, way_t *src) { |
25 | |
26 | /* transfer relation memberships from the src way to the dst one */ |
27 | relation_chain_t *rchain = osm_way_to_relation(osm, src); |
28 | |
29 | while(rchain) { |
30 | relation_chain_t *next = rchain->next; |
31 | printf("way #"ITEM_ID_FORMAT" is part of relation #"ITEM_ID_FORMAT"\n", |
32 | src->id, rchain->relation->id); |
33 | |
34 | /* make new member of the same relation */ |
35 | |
36 | /* walk member chain. save role of way if its being found. */ |
37 | member_t **member = &rchain->relation->member; |
38 | char *role = NULL; |
39 | while(*member) { |
40 | /* save role of way */ |
41 | if(((*member)->object.type == WAY) && ((*member)->object.way == src)) |
42 | role = (*member)->role; |
43 | member = &(*member)->next; |
44 | } |
45 | |
46 | printf(" adding way #"ITEM_ID_FORMAT" to relation\n", dst->id); |
47 | *member = g_new0(member_t, 1); |
48 | (*member)->object.type = WAY; |
49 | (*member)->object.way = dst; |
50 | if(role) (*member)->role = g_strdup(role); |
51 | member = &(*member)->next; |
52 | |
53 | rchain->relation->flags |= OSM_FLAG_DIRTY; |
54 | |
55 | g_free(rchain); |
56 | rchain = next; |
57 | } |
58 | } |
59 | |
60 | |
61 | /* combine tags from src to dst and combine them in a useful manner */ |
62 | /* erase all source tags */ |
63 | static gboolean combine_tags(tag_t **dst, tag_t *src) { |
64 | gboolean conflict = FALSE; |
65 | tag_t *dst_orig = *dst; |
66 | |
67 | /* ---------- transfer tags from way[1] to way[0] ----------- */ |
68 | while(*dst) dst = &((*dst)->next); /* find end of target tag list */ |
69 | while(src) { |
70 | /* check if same key but with different value is present, */ |
71 | /* ignoring the created_by tags */ |
72 | if(!osm_is_creator_tag(src) && |
73 | osm_tag_key_other_value_present(dst_orig, src)) |
74 | conflict = TRUE; |
75 | |
76 | /* don't copy "created_by" and "source" tag or tags that already */ |
77 | /* exist in identical form */ |
78 | if(osm_is_creator_tag(src) || |
79 | osm_tag_key_and_value_present(dst_orig, src)) { |
80 | tag_t *next = src->next; |
81 | osm_tag_free(src); |
82 | src = next; |
83 | } else { |
84 | *dst = src; |
85 | src = src->next; |
86 | dst = &((*dst)->next); |
87 | *dst = NULL; |
88 | } |
89 | } |
90 | return conflict; |
91 | } |
92 | |
93 | /* -------------------------- way_add ----------------------- */ |
94 | |
95 | void map_edit_way_add_begin(map_t *map, way_t *way_sel) { |
96 | if(way_sel) printf("previously selected way is #"ITEM_ID_FORMAT"\n", |
97 | way_sel->id); |
98 | |
99 | g_assert(!map->action.way); |
100 | map->action.way = osm_way_new(); |
101 | map->action.extending = NULL; |
102 | } |
103 | |
104 | void map_edit_way_add_segment(map_t *map, gint x, gint y) { |
105 | |
106 | /* convert mouse position to canvas (world) position */ |
107 | canvas_window2world(map->canvas, x, y, &x, &y); |
108 | |
109 | /* check if this was a double click. This is the case if */ |
110 | /* the last node placed is less than 5 pixels from the current */ |
111 | /* position */ |
112 | node_t *lnode = osm_way_get_last_node(map->action.way); |
113 | if(lnode && (map->state->zoom * sqrt((lnode->lpos.x-x)*(lnode->lpos.x-x)+ |
114 | (lnode->lpos.y-y)*(lnode->lpos.y-y))) < 5) { |
115 | #if 0 |
116 | printf("detected double click -> simulate ok click\n"); |
117 | map_hl_touchnode_clear(map); |
118 | map_action_ok(map->appdata); |
119 | #else |
120 | printf("detected double click -> ignore it as accidential\n"); |
121 | #endif |
122 | } else { |
123 | |
124 | /* use the existing node if one was touched */ |
125 | node_t *node = map_hl_touchnode_get_node(map); |
126 | if(node) { |
127 | printf(" re-using node #"ITEM_ID_FORMAT"\n", node->id); |
128 | map_hl_touchnode_clear(map); |
129 | |
130 | g_assert(map->action.way); |
131 | |
132 | /* check whether this node is first or last one of a different way */ |
133 | way_t *touch_way = NULL; |
134 | way_chain_t *way_chain = osm_node_to_way(map->appdata->osm, node); |
135 | while(way_chain) { |
136 | way_chain_t *next = way_chain->next; |
137 | |
138 | if(node == osm_way_get_last_node(way_chain->way)) { |
139 | printf(" way #"ITEM_ID_FORMAT" ends with this node\n", |
140 | way_chain->way->id); |
141 | if(!touch_way) touch_way = way_chain->way; |
142 | } |
143 | |
144 | if(node == osm_way_get_first_node(way_chain->way)) { |
145 | printf(" way #"ITEM_ID_FORMAT" starts with this node\n", |
146 | way_chain->way->id); |
147 | if(!touch_way) touch_way = way_chain->way; |
148 | } |
149 | |
150 | g_free(way_chain); |
151 | way_chain = next; |
152 | } |
153 | |
154 | /* remeber this way as this may be the last node placed */ |
155 | /* and we might want to join this with this other way */ |
156 | map->action.ends_on = touch_way; |
157 | |
158 | /* is this the first node the user places? */ |
159 | if(!map->action.way->node_chain) { |
160 | map->action.extending = touch_way; |
161 | |
162 | if(map->action.extending) { |
163 | if(!yes_no_f(GTK_WIDGET(map->appdata->window), |
164 | map->appdata, MISC_AGAIN_ID_EXTEND_WAY, 0, |
165 | _("Extend way?"), |
166 | _("Do you want to extend the way present at this location?"))) |
167 | map->action.extending = NULL; |
168 | else |
169 | /* there are immediately enough nodes for a valid way */ |
170 | icon_bar_map_cancel_ok(map->appdata, TRUE, TRUE); |
171 | } |
172 | } |
173 | |
174 | } else { |
175 | /* the current way doesn't end on another way if we are just placing */ |
176 | /* a new node */ |
177 | map->action.ends_on = NULL; |
178 | |
179 | if(!osm_position_within_bounds(map->appdata->osm, x, y)) |
180 | map_outside_error(map->appdata); |
181 | else |
182 | node = osm_node_new(map->appdata->osm, x, y); |
183 | } |
184 | |
185 | if(node) { |
186 | g_assert(map->action.way); |
187 | osm_way_append_node(map->action.way, node); |
188 | |
189 | switch(osm_node_chain_length(map->action.way->node_chain)) { |
190 | case 1: |
191 | /* replace "place first node..." message */ |
192 | statusbar_set(map->appdata, _("Place next node of way"), FALSE); |
193 | break; |
194 | |
195 | case 2: |
196 | /* two nodes are enough for a valid way */ |
197 | icon_bar_map_cancel_ok(map->appdata, TRUE, TRUE); |
198 | break; |
199 | |
200 | default: |
201 | break; |
202 | } |
203 | |
204 | /* remove prior version of this way */ |
205 | map_item_chain_destroy(&map->action.way->map_item_chain); |
206 | |
207 | /* draw current way */ |
208 | josm_elemstyles_colorize_way(map->style, map->action.way); |
209 | map_way_draw(map, map->action.way); |
210 | } |
211 | } |
212 | } |
213 | |
214 | void map_edit_way_add_cancel(map_t *map) { |
215 | |
216 | printf(" removing temporary way\n"); |
217 | g_assert(map->action.way); |
218 | |
219 | /* remove all nodes that have been created for this way */ |
220 | /* (their way count will be 0 after removing the way) */ |
221 | node_chain_t *chain = map->action.way->node_chain; |
222 | while(chain) { |
223 | node_chain_t *next = chain->next; |
224 | |
225 | printf(" node #"ITEM_ID_FORMAT" (used by %d)\n", |
226 | chain->node->id, chain->node->ways); |
227 | |
228 | chain->node->ways--; |
229 | if(!chain->node->ways && (chain->node->id == ID_ILLEGAL)) { |
230 | printf(" -> freeing temp node\n"); |
231 | osm_node_free(&map->appdata->icon, chain->node); |
232 | } |
233 | g_free(chain); |
234 | chain = next; |
235 | } |
236 | map->action.way->node_chain = NULL; |
237 | |
238 | /* remove ways visual representation */ |
239 | map_item_chain_destroy(&map->action.way->map_item_chain); |
240 | |
241 | osm_way_free(map->action.way); |
242 | map->action.way = NULL; |
243 | } |
244 | |
245 | void map_edit_way_add_ok(map_t *map) { |
246 | g_assert(map->action.way); |
247 | |
248 | /* transfer all nodes that have been created for this way */ |
249 | /* into the node chain */ |
250 | |
251 | /* (their way count will be 0 after removing the way) */ |
252 | node_chain_t *chain = map->action.way->node_chain; |
253 | while(chain) { |
254 | printf(" node #"ITEM_ID_FORMAT" (used by %d)\n", |
255 | chain->node->id, chain->node->ways); |
256 | |
257 | /* a node may have been a stand-alone node before, so remove its */ |
258 | /* visual representation as its now drawn as part of the way */ |
259 | /* (if at all) */ |
260 | if(chain->node->id != ID_ILLEGAL) |
261 | map_item_chain_destroy(&chain->node->map_item_chain); |
262 | |
263 | map_node_draw(map, chain->node); |
264 | |
265 | /* we can be sure that no node gets inserted twice (even if twice in */ |
266 | /* the ways chain) because it gets assigned a non-ID_ILLEGAL id when */ |
267 | /* being moved to the osm node chain */ |
268 | if(chain->node->id == ID_ILLEGAL) |
269 | osm_node_attach(map->appdata->osm, chain->node); |
270 | |
271 | chain = chain->next; |
272 | } |
273 | |
274 | /* attach to existing way if the user requested so */ |
275 | gboolean reverse = FALSE; |
276 | if(map->action.extending) { |
277 | node_t *nfirst = map->action.way->node_chain->node; |
278 | |
279 | printf(" request to extend way #"ITEM_ID_FORMAT"\n", |
280 | map->action.extending->id); |
281 | |
282 | if(osm_way_get_first_node(map->action.extending) == nfirst) { |
283 | printf(" need to prepend\n"); |
284 | osm_way_reverse(map->action.extending); |
285 | reverse = TRUE; |
286 | } else if(osm_way_get_last_node(map->action.extending) == nfirst) |
287 | printf(" need to append\n"); |
288 | |
289 | /* search end of way to be extended */ |
290 | node_chain_t *chain = map->action.extending->node_chain; |
291 | g_assert(chain); |
292 | while(chain->next) chain = chain->next; |
293 | |
294 | /* skip first node of new way as its the same as the last one of the */ |
295 | /* way we are attaching it to */ |
296 | chain->next = map->action.way->node_chain->next; |
297 | |
298 | /* terminate new way afer first node */ |
299 | map->action.way->node_chain->next = NULL; |
300 | |
301 | /* erase and free new way (now only containing the first node anymore) */ |
302 | map_item_chain_destroy(&map->action.way->map_item_chain); |
303 | osm_way_free(map->action.way); |
304 | |
305 | map->action.way = map->action.extending; |
306 | map->action.way->flags |= OSM_FLAG_DIRTY; |
307 | |
308 | /* and undo reversion of required */ |
309 | if(reverse) |
310 | osm_way_reverse(map->action.way); |
311 | |
312 | } else { |
313 | /* now move the way itself into the main data structure */ |
314 | osm_way_attach(map->appdata->osm, map->action.way); |
315 | } |
316 | |
317 | /* we might already be working on the "ends_on" way as we may */ |
318 | /* be extending it. Joining the same way doesn't make sense. */ |
319 | if(map->action.ends_on && (map->action.ends_on == map->action.way)) { |
320 | printf(" the new way ends on itself -> don't join itself\n"); |
321 | map->action.ends_on = NULL; |
322 | } |
323 | |
324 | if(map->action.ends_on) |
325 | if(!yes_no_f(GTK_WIDGET(map->appdata->window), |
326 | map->appdata, MISC_AGAIN_ID_EXTEND_WAY_END, 0, |
327 | _("Join way?"), |
328 | _("Do you want to join the way present at this location?"))) |
329 | map->action.ends_on = NULL; |
330 | |
331 | if(map->action.ends_on) { |
332 | printf(" this new way ends on another way\n"); |
333 | |
334 | /* If reverse is true the node in question is the first one */ |
335 | /* of the newly created way. Thus it is reversed again before */ |
336 | /* attaching and the result is finally reversed once more */ |
337 | |
338 | /* this is slightly more complex as this time two full tagged */ |
339 | /* ways may be involved as the new way may be an extended existing */ |
340 | /* way being connected to another way. This happens if you connect */ |
341 | /* two existing ways using a new way between them */ |
342 | |
343 | if (reverse) osm_way_reverse(map->action.way); |
344 | |
345 | /* and open dialog to resolve tag collisions if necessary */ |
346 | if(combine_tags(&map->action.way->tag, map->action.ends_on->tag)) |
347 | messagef(GTK_WIDGET(map->appdata->window), _("Way tag conflict"), |
348 | _("The resulting way contains some conflicting tags. " |
349 | "Please solve these.")); |
350 | |
351 | map->action.ends_on->tag = NULL; |
352 | |
353 | /* make way member of all relations ends_on already is */ |
354 | transfer_relations(map->appdata->osm, map->action.way, map->action.ends_on); |
355 | |
356 | /* check if we have to reverse (again?) to match the way order */ |
357 | if(osm_way_get_last_node(map->action.ends_on) == |
358 | osm_way_get_last_node(map->action.way)) { |
359 | |
360 | printf(" need to prepend ends_on\n"); |
361 | |
362 | /* need to reverse ends_on way */ |
363 | osm_way_reverse(map->action.ends_on); |
364 | reverse = !reverse; |
365 | } |
366 | |
367 | /* search end of node chain */ |
368 | node_chain_t *chain = map->action.way->node_chain; |
369 | g_assert(chain); |
370 | while(chain->next) chain = chain->next; |
371 | |
372 | /* attach ends_on way to way but omit first node */ |
373 | chain->next = map->action.ends_on->node_chain->next; |
374 | map->action.ends_on->node_chain->next = NULL; |
375 | |
376 | /* erase and free ends_on (now only containing the first node anymore) */ |
377 | map_way_delete(map->appdata, map->action.ends_on); |
378 | |
379 | if(reverse) osm_way_reverse(map->action.way); |
380 | } |
381 | |
382 | /* remove prior version of this way */ |
383 | map_item_chain_destroy(&map->action.way->map_item_chain); |
384 | |
385 | /* draw the updated way */ |
386 | map_way_draw(map, map->action.way); |
387 | |
388 | map_way_select(map->appdata, map->action.way); |
389 | |
390 | map->action.way = NULL; |
391 | |
392 | /* let the user specify some tags for the new way */ |
393 | info_dialog(GTK_WIDGET(map->appdata->window), map->appdata, NULL); |
394 | } |
395 | |
396 | /* -------------------------- way_node_add ----------------------- */ |
397 | |
398 | void map_edit_way_node_add_highlight(map_t *map, map_item_t *item, |
399 | gint x, gint y) { |
400 | if(map_item_is_selected_way(map, item)) { |
401 | gint nx, ny; |
402 | canvas_window2world(map->canvas, x, y, &nx, &ny); |
403 | if(canvas_item_get_segment(item->item, nx, ny) >= 0) |
404 | map_hl_cursor_draw(map, x, y, FALSE, map->style->node.radius); |
405 | } |
406 | } |
407 | |
408 | void map_edit_way_node_add(map_t *map, gint x, gint y) { |
409 | /* check if we are still hovering above the selected way */ |
410 | map_item_t *item = map_item_at(map, x, y); |
411 | if(item && map_item_is_selected_way(map, item)) { |
412 | /* convert mouse position to canvas (world) position */ |
413 | canvas_window2world(map->canvas, x, y, &x, &y); |
414 | gint insert_after = canvas_item_get_segment(item->item, x, y)+1; |
415 | if(insert_after > 0) { |
416 | /* create new node */ |
417 | node_t* node = osm_node_new(map->appdata->osm, x, y); |
418 | osm_node_attach(map->appdata->osm, node); |
419 | |
420 | /* insert it into ways chain of nodes */ |
421 | way_t *way = item->object.way; |
422 | |
423 | /* search correct position */ |
424 | node_chain_t **chain = &way->node_chain; |
425 | while(insert_after--) { |
426 | g_assert(*chain); |
427 | chain = &(*chain)->next; |
428 | } |
429 | |
430 | /* and actually do the insertion */ |
431 | node_chain_t *new_chain_item = g_new0(node_chain_t, 1); |
432 | new_chain_item->node = node; |
433 | new_chain_item->next = *chain; |
434 | *chain = new_chain_item; |
435 | |
436 | /* clear selection */ |
437 | map_item_deselect(map->appdata); |
438 | |
439 | /* remove prior version of this way */ |
440 | map_item_chain_destroy(&way->map_item_chain); |
441 | |
442 | /* draw the updated way */ |
443 | map_way_draw(map, way); |
444 | |
445 | /* remember that this node is contained in one way */ |
446 | node->ways=1; |
447 | |
448 | /* and that the way needs to be uploaded */ |
449 | way->flags |= OSM_FLAG_DIRTY; |
450 | |
451 | /* put gui into idle state */ |
452 | map_action_set(map->appdata, MAP_ACTION_IDLE); |
453 | |
454 | /* and redo it */ |
455 | map_way_select(map->appdata, way); |
456 | } |
457 | } |
458 | } |
459 | |
460 | /* -------------------------- way_node_cut ----------------------- */ |
461 | |
462 | void map_edit_way_cut_highlight(map_t *map, map_item_t *item, gint x, gint y) { |
463 | |
464 | if(map_item_is_selected_way(map, item)) { |
465 | gint nx, ny, seg; |
466 | canvas_window2world(map->canvas, x, y, &nx, &ny); |
467 | seg = canvas_item_get_segment(item->item, nx, ny); |
468 | if(seg >= 0) { |
469 | gint x0, y0, x1, y1; |
470 | canvas_item_get_segment_pos(item->item, seg, &x0, &y0, &x1, &y1); |
471 | |
472 | gint width = (item->object.way->draw.flags & |
473 | OSM_DRAW_FLAG_BG)? |
474 | 2*item->object.way->draw.bg.width: |
475 | 3*item->object.way->draw.width; |
476 | map_hl_segment_draw(map, width, x0, y0, x1, y1); |
477 | } |
478 | } else if(map_item_is_selected_node(map, item)) { |
479 | node_t *nfirst = osm_way_get_first_node(map->selected.object.way); |
480 | node_t *nlast = osm_way_get_last_node(map->selected.object.way); |
481 | |
482 | /* cutting a way at its first or last node doesn't make much sense ... */ |
483 | if((nfirst != item->object.node) && (nlast != item->object.node)) |
484 | map_hl_cursor_draw(map, item->object.node->lpos.x, item->object.node->lpos.y, |
485 | TRUE, 2*map->style->node.radius); |
486 | } |
487 | } |
488 | |
489 | /* cut the currently selected way at the current cursor position */ |
490 | void map_edit_way_cut(map_t *map, gint x, gint y) { |
491 | |
492 | /* check if we are still hovering above the selected way */ |
493 | map_item_t *item = map_item_at(map, x, y); |
494 | if(item && (map_item_is_selected_way(map, item) || |
495 | map_item_is_selected_node(map, item))) { |
496 | gboolean cut_at_node = map_item_is_selected_node(map, item); |
497 | |
498 | /* convert mouse position to canvas (world) position */ |
499 | canvas_window2world(map->canvas, x, y, &x, &y); |
500 | |
501 | gint cut_at = -1; |
502 | way_t *way = NULL; |
503 | if(cut_at_node) { |
504 | printf(" cut at node\n"); |
505 | |
506 | /* node must not be first or last node of way */ |
507 | g_assert(map->selected.object.type == WAY); |
508 | |
509 | if((osm_way_get_first_node(map->selected.object.way) != |
510 | item->object.node) && |
511 | (osm_way_get_last_node(map->selected.object.way) != |
512 | item->object.node)) { |
513 | way = map->selected.object.way; |
514 | |
515 | cut_at = 0; |
516 | node_chain_t *chain = way->node_chain; |
517 | while(chain && chain->node != item->object.node) { |
518 | chain = chain->next; |
519 | cut_at++; |
520 | } |
521 | |
522 | } else |
523 | printf(" won't cut as it's last or first node\n"); |
524 | |
525 | } else { |
526 | printf(" cut at segment\n"); |
527 | cut_at = canvas_item_get_segment(item->item, x, y); |
528 | if(cut_at >= 0) way = item->object.way; |
529 | } |
530 | |
531 | if(way) { |
532 | /* create a duplicate of the currently selected way */ |
533 | way_t *new = osm_way_new(); |
534 | |
535 | /* if this is a closed way, reorder (rotate) it, so the */ |
536 | /* place to cut is adjecent to the begin/end of the way. */ |
537 | /* this prevents a cut polygon to be split into two ways */ |
538 | g_assert(way->node_chain); |
539 | if(way->node_chain->node == osm_way_get_last_node(way)) { |
540 | printf("CLOSED WAY -> rotate by %d\n", cut_at); |
541 | osm_way_rotate(way, cut_at); |
542 | cut_at = 0; |
543 | } |
544 | |
545 | /* ------------ copy all tags ------------- */ |
546 | new->tag = osm_tags_copy(way->tag, TRUE); |
547 | |
548 | /* ---- transfer relation membership from way to new ----- */ |
549 | transfer_relations(map->appdata->osm, new, way); |
550 | |
551 | /* move parts of node_chain to the new way */ |
552 | printf(" moving everthing after segment %d to new way\n", cut_at); |
553 | |
554 | node_chain_t *chain = way->node_chain; |
555 | while(cut_at--) { |
556 | g_assert(chain); |
557 | chain = chain->next; |
558 | } |
559 | |
560 | /* attach remaining nodes to new way */ |
561 | new->node_chain = chain->next; |
562 | |
563 | /* terminate remainig chain on old way */ |
564 | chain->next = NULL; |
565 | |
566 | /* if we cut at a node, this node is now part of both ways. so */ |
567 | /* create a copy of the last node of the old way and prepend it to */ |
568 | /* the new way */ |
569 | if(cut_at_node) { |
570 | node_chain_t *first = g_new0(node_chain_t, 1); |
571 | first->next = new->node_chain; |
572 | first->node = osm_way_get_last_node(way); |
573 | first->node->ways++; |
574 | new->node_chain = first; |
575 | } |
576 | |
577 | /* now move the way itself into the main data structure */ |
578 | osm_way_attach(map->appdata->osm, new); |
579 | |
580 | /* clear selection */ |
581 | map_item_deselect(map->appdata); |
582 | |
583 | /* remove prior version of this way */ |
584 | printf("remove visible version of way #"ITEM_ID_FORMAT"\n", way->id); |
585 | map_item_chain_destroy(&way->map_item_chain); |
586 | |
587 | /* swap chains of the old way is to be destroyed due to a lack */ |
588 | /* of nodes */ |
589 | if(osm_way_number_of_nodes(way) < 2) { |
590 | printf("swapping ways to avoid destruction of original way\n"); |
591 | node_chain_t *tmp = way->node_chain; |
592 | way->node_chain = new->node_chain; |
593 | new->node_chain = tmp; |
594 | } |
595 | |
596 | /* the way may still only consist of a single node. */ |
597 | /* remove it then */ |
598 | if(osm_way_number_of_nodes(way) < 2) { |
599 | printf("original way has less than 2 nodes left, deleting it\n"); |
600 | map_way_delete(map->appdata, way); |
601 | item = NULL; |
602 | } else { |
603 | printf("original way still has %d nodes\n", |
604 | osm_way_number_of_nodes(way)); |
605 | |
606 | /* draw the updated old way */ |
607 | josm_elemstyles_colorize_way(map->style, way); |
608 | map_way_draw(map, way); |
609 | |
610 | /* remember that the way needs to be uploaded */ |
611 | way->flags |= OSM_FLAG_DIRTY; |
612 | } |
613 | |
614 | if(osm_way_number_of_nodes(new) < 2) { |
615 | printf("new way has less than 2 nodes, deleting it\n"); |
616 | map_way_delete(map->appdata, new); |
617 | new = NULL; |
618 | } else { |
619 | |
620 | /* colorize the new way before drawing */ |
621 | josm_elemstyles_colorize_way(map->style, new); |
622 | map_way_draw(map, new); |
623 | } |
624 | |
625 | /* put gui into idle state */ |
626 | map_action_set(map->appdata, MAP_ACTION_IDLE); |
627 | |
628 | /* and redo selection if way still exists */ |
629 | if(item) |
630 | map_way_select(map->appdata, way); |
631 | else if(new) |
632 | map_way_select(map->appdata, new); |
633 | } |
634 | } |
635 | } |
636 | |
637 | void map_edit_node_move(appdata_t *appdata, map_item_t *map_item, |
638 | gint ex, gint ey) { |
639 | |
640 | map_t *map = appdata->map; |
641 | osm_t *osm = appdata->osm; |
642 | |
643 | g_assert(map_item->object.type == NODE); |
644 | node_t *node = map_item->object.node; |
645 | |
646 | printf("released dragged node #"ITEM_ID_FORMAT"\n", node->id); |
647 | printf(" was at %d %d (%f %f)\n", |
648 | node->lpos.x, node->lpos.y, |
649 | node->pos.lat, node->pos.lon); |
650 | |
651 | |
652 | /* check if it was dropped onto another node */ |
653 | node_t *touchnode = map_hl_touchnode_get_node(map); |
654 | gboolean joined_with_touchnode = FALSE; |
655 | |
656 | if(touchnode) { |
657 | map_hl_touchnode_clear(map); |
658 | |
659 | printf(" dropped onto node #"ITEM_ID_FORMAT"\n", touchnode->id); |
660 | |
661 | if(yes_no_f(GTK_WIDGET(appdata->window), |
662 | appdata, MISC_AGAIN_ID_JOIN_NODES, 0, |
663 | _("Join nodes?"), |
664 | _("Do you want to join the dragged node with the one " |
665 | "you dropped it on?"))) { |
666 | |
667 | /* the touchnode vanishes and is replaced by the node the */ |
668 | /* user dropped onto it */ |
669 | joined_with_touchnode = TRUE; |
670 | |
671 | /* use touchnodes position */ |
672 | node->lpos = touchnode->lpos; |
673 | node->pos = touchnode->pos; |
674 | |
675 | way_t *way = appdata->osm->way; |
676 | while(way) { |
677 | node_chain_t *chain = way->node_chain; |
678 | while(chain) { |
679 | if(chain->node == touchnode) { |
680 | printf(" found node in way #"ITEM_ID_FORMAT"\n", way->id); |
681 | |
682 | /* replace by node */ |
683 | chain->node = node; |
684 | |
685 | /* and adjust way references of both nodes */ |
686 | node->ways++; |
687 | touchnode->ways--; |
688 | |
689 | way->flags |= OSM_FLAG_DIRTY; |
690 | } |
691 | chain = chain->next; |
692 | } |
693 | way = way->next; |
694 | } |
695 | |
696 | /* replace node in relations */ |
697 | relation_t *relation = appdata->osm->relation; |
698 | while(relation) { |
699 | member_t *member = relation->member; |
700 | while(member) { |
701 | if(member->object.type == NODE && member->object.node == touchnode) { |
702 | printf(" found node in relation #"ITEM_ID_FORMAT"\n", |
703 | relation->id); |
704 | |
705 | /* replace by node */ |
706 | member->object.node = node; |
707 | |
708 | relation->flags |= OSM_FLAG_DIRTY; |
709 | } |
710 | member = member->next; |
711 | } |
712 | relation = relation->next; |
713 | } |
714 | |
715 | gboolean conflict = combine_tags(&node->tag, touchnode->tag); |
716 | touchnode->tag = NULL; |
717 | |
718 | /* touchnode must not have any references to ways anymore */ |
719 | g_assert(!touchnode->ways); |
720 | |
721 | /* delete touchnode */ |
722 | /* remove it visually from the screen */ |
723 | map_item_chain_destroy(&touchnode->map_item_chain); |
724 | |
725 | /* and remove it from the data structures */ |
726 | osm_node_remove_from_relation(appdata->osm, touchnode); |
727 | osm_node_delete(appdata->osm, &appdata->icon, touchnode, FALSE, TRUE); |
728 | |
729 | /* and open dialog to resolve tag collisions if necessary */ |
730 | if(conflict) |
731 | messagef(GTK_WIDGET(appdata->window), _("Node tag conflict"), |
732 | _("The resulting node contains some conflicting tags. " |
733 | "Please solve these.")); |
734 | |
735 | /* check whether this will also join two ways */ |
736 | printf(" checking if node is end of way\n"); |
737 | guint ways2join_cnt = 0; |
738 | way_t *ways2join[2] = { NULL, NULL }; |
739 | way = appdata->osm->way; |
740 | while(way) { |
741 | if(osm_way_ends_with_node(way, node)) { |
742 | if(ways2join_cnt < 2) |
743 | ways2join[ways2join_cnt] = way; |
744 | |
745 | printf(" way #"ITEM_ID_FORMAT" ends with this node\n", way->id); |
746 | ways2join_cnt++; |
747 | } |
748 | way = way->next; |
749 | } |
750 | |
751 | if(ways2join_cnt > 2) { |
752 | messagef(GTK_WIDGET(appdata->window), _("Too many ways to join"), |
753 | _("More than two ways now end on this node. Joining more " |
754 | "than two ways is not yet implemented, sorry")); |
755 | |
756 | } else if(ways2join_cnt == 2) { |
757 | if(yes_no_f(GTK_WIDGET(appdata->window), |
758 | appdata, MISC_AGAIN_ID_JOIN_WAYS, 0, |
759 | _("Join ways?"), |
760 | _("Do you want to join the dragged way with the one " |
761 | "you dropped it on?"))) { |
762 | |
763 | printf(" about to join ways #" ITEM_ID_FORMAT " and #" |
764 | ITEM_ID_FORMAT "\n", ways2join[0]->id, ways2join[1]->id); |
765 | |
766 | /* way[1] gets destroyed and attached to way[0] */ |
767 | /* so check if way[1] is selected and exchainge ways then */ |
768 | /* so that way may stay selected */ |
769 | if((map->selected.object.type == WAY) && |
770 | (map->selected.object.way == ways2join[1])) { |
771 | printf(" swapping ways to keep selected one alive\n"); |
772 | way_t *tmp = ways2join[1]; |
773 | ways2join[1] = ways2join[0]; |
774 | ways2join[0] = tmp; |
775 | } |
776 | |
777 | /* take all nodes from way[1] and append them to way[0] */ |
778 | /* check if we have to append or prepend to way[0] */ |
779 | gboolean reverse = FALSE; |
780 | if(ways2join[0]->node_chain->node == node) { |
781 | /* make "prepend" to be "append" by reversing way[0] */ |
782 | printf(" target prepend -> reverse\n"); |
783 | reverse = TRUE; |
784 | osm_way_reverse(ways2join[0]); |
785 | } |
786 | |
787 | /* verify the common node is last in the target way */ |
788 | node_chain_t *chain = ways2join[0]->node_chain; |
789 | while(chain->next) chain = chain->next; |
790 | g_assert(chain->node == node); |
791 | g_assert(!chain->next); |
792 | |
793 | /* common node must be first in the chain to attach */ |
794 | if(ways2join[1]->node_chain->node != node) { |
795 | printf(" source reverse\n"); |
796 | osm_way_reverse(ways2join[1]); |
797 | } |
798 | |
799 | /* verify the common node is first in the source way */ |
800 | g_assert(ways2join[1]->node_chain->node == node); |
801 | |
802 | /* finally append source chain to target */ |
803 | g_assert(!chain->next); |
804 | chain->next = ways2join[1]->node_chain->next; |
805 | |
806 | ways2join[1]->node_chain->next = NULL; |
807 | |
808 | /* transfer tags from touchnode to node */ |
809 | |
810 | /* ---------- transfer tags from way[1] to way[0] ----------- */ |
811 | gboolean conflict = |
812 | combine_tags(&ways2join[0]->tag, ways2join[1]->tag); |
813 | ways2join[1]->tag = NULL; |
814 | |
815 | /* ---- transfer relation membership from way[1] to way[0] ----- */ |
816 | relation_chain_t *rchain = |
817 | osm_way_to_relation(appdata->osm, ways2join[1]); |
818 | |
819 | while(rchain) { |
820 | relation_chain_t *next = rchain->next; |
821 | printf("way[1] is part of relation #"ITEM_ID_FORMAT"\n", |
822 | rchain->relation->id); |
823 | |
824 | /* make way[0] member of the same relation */ |
825 | |
826 | /* walk member chain. save role of way[1] if its being found. */ |
827 | /* end search either at end of chain or if way[0] was foind */ |
828 | /* as it's already a member of that relation */ |
829 | member_t **member = &rchain->relation->member; |
830 | char *role = NULL; |
831 | while(*member && |
832 | !(((*member)->object.type == WAY) && |
833 | ((*member)->object.way == ways2join[0]))) { |
834 | |
835 | /* save role of way[1] */ |
836 | if(((*member)->object.type == WAY) && |
837 | ((*member)->object.way == ways2join[0])) |
838 | role = (*member)->role; |
839 | |
840 | member = &(*member)->next; |
841 | } |
842 | |
843 | if(*member) |
844 | printf(" both ways were members of this relation\n"); |
845 | else { |
846 | printf(" adding way[0] to relation\n"); |
847 | *member = g_new0(member_t, 1); |
848 | (*member)->object.type = WAY; |
849 | (*member)->object.way = ways2join[0]; |
850 | if(role) (*member)->role = g_strdup(role); |
851 | member = &(*member)->next; |
852 | |
853 | rchain->relation->flags |= OSM_FLAG_DIRTY; |
854 | } |
855 | |
856 | g_free(rchain); |
857 | rchain = next; |
858 | } |
859 | |
860 | |
861 | /* and open dialog to resolve tag collisions if necessary */ |
862 | if(conflict) |
863 | messagef(GTK_WIDGET(appdata->window), _("Way tag conflict"), |
864 | _("The resulting way contains some conflicting tags. " |
865 | "Please solve these.")); |
866 | |
867 | ways2join[0]->flags |= OSM_FLAG_DIRTY; |
868 | map_way_delete(appdata, ways2join[1]); |
869 | } |
870 | } |
871 | } |
872 | } |
873 | |
874 | /* the node either wasn't dropped into another one (touchnode) or */ |
875 | /* the user didn't want to join the nodes */ |
876 | if(!joined_with_touchnode) { |
877 | |
878 | /* finally update dragged nodes position */ |
879 | |
880 | /* convert mouse position to canvas (world) position */ |
881 | gint x, y; |
882 | canvas_window2world(map->canvas, ex, ey, &x, &y); |
883 | if(!osm_position_within_bounds(appdata->osm, x, y)) { |
884 | map_outside_error(appdata); |
885 | return; |
886 | } |
887 | |
888 | node->lpos.x = x; |
889 | node->lpos.y = y; |
890 | |
891 | /* convert screen position to lat/lon */ |
892 | lpos2pos(osm->bounds, &node->lpos, &node->pos); |
893 | |
894 | /* convert pos back to lpos to see rounding errors */ |
895 | pos2lpos(osm->bounds, &node->pos, &node->lpos); |
896 | |
897 | printf(" now at %d %d (%f %f)\n", |
898 | node->lpos.x, node->lpos.y, node->pos.lat, node->pos.lon); |
899 | } |
900 | |
901 | /* now update the visual representation of the node */ |
902 | |
903 | map_item_chain_destroy(&node->map_item_chain); |
904 | map_node_draw(map, node); |
905 | |
906 | /* visually update ways, node is part of */ |
907 | way_t *way = osm->way; |
908 | while(way) { |
909 | if(osm_node_in_way(way, node)) { |
910 | printf(" node is part of way #"ITEM_ID_FORMAT", redraw!\n", way->id); |
911 | |
912 | /* remove prior version of this way */ |
913 | map_item_chain_destroy(&way->map_item_chain); |
914 | |
915 | /* draw current way */ |
916 | josm_elemstyles_colorize_way(map->style, way); |
917 | map_way_draw(map, way); |
918 | } |
919 | |
920 | way = way->next; |
921 | } |
922 | |
923 | /* and mark the node as dirty */ |
924 | node->flags |= OSM_FLAG_DIRTY; |
925 | |
926 | /* update highlight */ |
927 | map_highlight_refresh(appdata); |
928 | } |
929 | |
930 | /* -------------------------- way_reverse ----------------------- */ |
931 | |
932 | /* called from the "reverse" icon */ |
933 | void map_edit_way_reverse(appdata_t *appdata) { |
934 | /* work on local copy since de-selecting destroys the selection */ |
935 | map_item_t item = appdata->map->selected; |
936 | |
937 | /* deleting the selected item de-selects it ... */ |
938 | map_item_deselect(appdata); |
939 | |
940 | g_assert(item.object.type == WAY); |
941 | |
942 | osm_way_reverse(item.object.way); |
943 | guint n_tags_flipped = |
944 | osm_way_reverse_direction_sensitive_tags(item.object.way); |
945 | guint n_roles_flipped = |
946 | osm_way_reverse_direction_sensitive_roles(appdata->osm, item.object.way); |
947 | |
948 | item.object.way->flags |= OSM_FLAG_DIRTY; |
949 | map_way_select(appdata, item.object.way); |
950 | |
951 | // Flash a message about any side-effects |
952 | char *msg = NULL; |
953 | if (n_tags_flipped && !n_roles_flipped) { |
954 | msg = g_strdup_printf(ngettext("%d tag updated", "%d tags updated", |
955 | n_tags_flipped), |
956 | n_tags_flipped); |
957 | } |
958 | else if (!n_tags_flipped && n_roles_flipped) { |
959 | msg = g_strdup_printf(ngettext("%d relation updated", |
960 | "%d relations updated", |
961 | n_roles_flipped), |
962 | n_roles_flipped); |
963 | } |
964 | else if (n_tags_flipped && n_roles_flipped) { |
965 | char *msg1 = g_strdup_printf(ngettext("%d tag", "%d tags", |
966 | n_tags_flipped), |
967 | n_tags_flipped); |
968 | char *msg2 = g_strdup_printf(ngettext("%d relation", "%d relations", |
969 | n_roles_flipped), |
970 | n_roles_flipped); |
971 | msg = g_strdup_printf(_("%s & %s updated"), msg1, msg2); |
972 | g_free(msg1); |
973 | g_free(msg2); |
974 | } |
975 | if (msg) { |
976 | banner_show_info(appdata, msg); |
977 | g_free(msg); |
978 | } |
979 | } |
980 | |
981 | // vim:et:ts=8:sw=2:sts=2:ai |