[gnome-maps/wip/osm-edit] osmEdit: WIP, implement editing objects in OSM.
- From: Marcus Lundblad <mlundblad src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-maps/wip/osm-edit] osmEdit: WIP, implement editing objects in OSM.
- Date: Wed, 16 Sep 2015 19:45:59 +0000 (UTC)
commit d1f6ab2b3529bf1981c7654ccdd2c48bdb25158f
Author: Marcus Lundblad <ml update uu se>
Date: Mon Jan 19 22:24:34 2015 +0100
osmEdit: WIP, implement editing objects in OSM.
configure.ac | 3 +-
data/org.gnome.Maps.data.gresource.xml | 1 +
data/ui/map-bubble.ui | 446 +++++++++++++++++++++++-------
data/ui/osm-edit-dialog.ui | 184 ++++++++++++
lib/Makefile.am | 16 +-
lib/maps-osm-changeset.c | 61 ++++
lib/maps-osm-changeset.h | 51 ++++
lib/maps-osm-node.c | 203 +++++++++++++
lib/maps-osm-node.h | 53 ++++
lib/maps-osm-object.c | 378 +++++++++++++++++++++++++
lib/maps-osm-object.h | 63 ++++
lib/maps-osm-relation.c | 157 ++++++++++
lib/maps-osm-relation.h | 58 ++++
lib/maps-osm-way.c | 116 ++++++++
lib/maps-osm-way.h | 49 ++++
lib/maps-osm.c | 487 ++++++++++++++++++++++++++++++++
lib/maps-osm.h | 36 +++
src/application.js | 3 +
src/mapBubble.js | 41 +++-
src/org.gnome.Maps.src.gresource.xml | 4 +
src/osmConnection.js | 254 +++++++++++++++++
src/osmEdit.js | 112 ++++++++
src/osmEditDialog.js | 122 ++++++++
src/osmUtils.js | 57 ++++
src/placeBubble.js | 332 ++++++++++++++++++++++-
25 files changed, 3178 insertions(+), 109 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 42f21a8..2152d1d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -29,7 +29,7 @@ AC_SUBST(GLIB_MKENUMS)
GIO_MIN_VERSION=2.39.3
GJS_MIN_VERSION=1.43.3
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
-GTK_MIN_VERSION=3.15.9
+GTK_MIN_VERSION=3.17.4
GEOCLUE_MIN_VERSION=0.12.99
PKG_CHECK_MODULES(GNOME_MAPS, [
@@ -50,6 +50,7 @@ PKG_CHECK_MODULES(GNOME_MAPS_LIB, [
folks >= $FOLKS_MIN_VERSION
geocode-glib-1.0 >= $GEOCODE_MIN_VERSION
champlain-0.12 >= $CHAMPLAIN_MIN_VERSION
+ libxml-2.0
])
AC_SUBST(GNOME_MAPS_LIB_CFLAGS)
AC_SUBST(GNOME_MAPS_LIB_LIBS)
diff --git a/data/org.gnome.Maps.data.gresource.xml b/data/org.gnome.Maps.data.gresource.xml
index 6ba6040..eb68093 100644
--- a/data/org.gnome.Maps.data.gresource.xml
+++ b/data/org.gnome.Maps.data.gresource.xml
@@ -13,6 +13,7 @@
<file preprocess="xml-stripblanks">ui/main-window.ui</file>
<file preprocess="xml-stripblanks">ui/map-bubble.ui</file>
<file preprocess="xml-stripblanks">ui/notification.ui</file>
+ <file preprocess="xml-stripblanks">ui/osm-edit-dialog.ui</file>
<file preprocess="xml-stripblanks">ui/place-bubble.ui</file>
<file preprocess="xml-stripblanks">ui/place-entry.ui</file>
<file preprocess="xml-stripblanks">ui/place-list-row.ui</file>
diff --git a/data/ui/map-bubble.ui b/data/ui/map-bubble.ui
index e488412..7bf8042 100644
--- a/data/ui/map-bubble.ui
+++ b/data/ui/map-bubble.ui
@@ -2,123 +2,373 @@
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
- <object class="GtkGrid" id="bubble-main-grid">
+ <object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_start">10</property>
- <property name="margin_end">10</property>
- <property name="margin_top">10</property>
- <property name="margin_bottom">10</property>
- <property name="orientation">vertical</property>
<child>
- <object class="GtkImage" id="bubble-image">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="valign">start</property>
- <property name="pixel_size">0</property>
- <property name="icon_size">16</property>
+ <object class="GtkGrid" id="bubble-main-grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">10</property>
+ <property name="margin_end">10</property>
+ <property name="margin_top">10</property>
+ <property name="margin_bottom">10</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkImage" id="bubble-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="pixel_size">0</property>
+ <property name="icon_size">16</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="bubble-content-area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin-start">15</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="bubble-button-area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin-top">10</property>
+ <property name="margin-start">15</property>
+ <child>
+ <object class="GtkBox" id="bubble-standard-button-area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkButton" id="bubble-route-button">
+ <property name="name">bubble-route-button</property>
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip-text" translatable="yes">Add to new route</property>
+ <child>
+ <object class="GtkImage" id="bubble-route-button-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">route-button-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-send-to-button">
+ <property name="name">bubble-send-to-button</property>
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip-text" translatable="yes">Open with another application</property>
+ <child>
+ <object class="GtkImage" id="bubble-send-to-button-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">send-to-symbolic</property>
+ <property name="pixel_size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="bubble-favorite-button">
+ <property name="name">bubble-favorite-button</property>
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip-text" translatable="yes">Mark as favorite</property>
+ <child>
+ <object class="GtkImage" id="bubble-favorite-button-image">
+ <property name="name">bubble-favorite-button-image</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">emblem-favorite-symbolic</property>
+ <property name="pixel_size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-check-in-button">
+ <property name="label" translatable="yes" comments="Translators: Check in is used as a
verb">C_heck in</property>
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="tooltip-text" translatable="yes">Check in here</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-edit-button">
+ <property name="name">bubble-edit-button"</property>
+ <property name="label" translatable="yes">Edit</property>
+ <!-- TODO: this button should be invisible by default
+ when we handle OSM accounts -->
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
</object>
<packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
+ <property name="name">view</property>
</packing>
+
</child>
<child>
- <object class="GtkGrid" id="bubble-content-area">
+ <object class="GtkSpinner" id="spinner">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin-start">15</property>
+ <property name="active">True</property>
</object>
<packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
+ <property name="name">loading</property>
</packing>
</child>
<child>
- <object class="GtkGrid" id="bubble-button-area">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="margin-top">10</property>
- <property name="margin-start">15</property>
- <child>
- <object class="GtkBox" id="bubble-standard-button-area">
+ <object class="GtkBox" id="bubble-edit-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">10</property>
+ <property name="margin_end">10</property>
+ <property name="margin_top">10</property>
+ <property name="margin_bottom">10</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkGrid" id="bubble-edit-content-area">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="orientation">horizontal</property>
- <property name="spacing">5</property>
- <child>
- <object class="GtkButton" id="bubble-route-button">
- <property name="name">bubble-route-button</property>
- <property name="visible">False</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="tooltip-text" translatable="yes">Add to new route</property>
- <child>
- <object class="GtkImage" id="bubble-route-button-image">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon-name">route-button-symbolic</property>
- <property name="icon_size">1</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="bubble-send-to-button">
- <property name="name">bubble-send-to-button</property>
- <property name="visible">False</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="tooltip-text" translatable="yes">Open with another application</property>
- <child>
- <object class="GtkImage" id="bubble-send-to-button-image">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon-name">send-to-symbolic</property>
- <property name="pixel_size">16</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkToggleButton" id="bubble-favorite-button">
- <property name="name">bubble-favorite-button</property>
- <property name="visible">False</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="tooltip-text" translatable="yes">Mark as favorite</property>
- <child>
- <object class="GtkImage" id="bubble-favorite-button-image">
- <property name="name">bubble-favorite-button-image</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="icon-name">emblem-favorite-symbolic</property>
- <property name="pixel_size">16</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="bubble-check-in-button">
- <property name="label" translatable="yes" comments="Translators: Check in is used as a
verb">C_heck in</property>
- <property name="visible">False</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_underline">True</property>
- <property name="tooltip-text" translatable="yes">Check in here</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
+ <property name="margin-start">15</property>
+ <property name="column-spacing">5</property>
+ <property name="row-spacing">5</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkGrid" id="bubble-edit-comment-grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin-top">10</property>
+ <property name="margin-start">15</property>
+ <property name="column-spacing">5</property>
+ <property name="row-spacing">5</property>
+
+ <child>
+ <object class="GtkLabel" id="bubble-edit-comment-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="True">Comment</property>
+ <property name="halign">GTK_ALIGN_END</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkEntry" id="bubble-edit-comment-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="placeholder-text">Describe changes…</property>
+ <property name="expand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="bubble-edit-button-area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin-top">10</property>
+ <property name="margin-start">15</property>
+ <child>
+ <object class="GtkBox" id="bubble-edit-button-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <object class="GtkMenuButton" id="bubble-add-detail-button">
+ <property name="name">bubble-add-detail-button"</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="popover">add-detail-popover</property>
+ <property name="direction">GTK_ARROW_UP</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">Add detail</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon-name">go-down-symbolic</property>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+
+ </child>
+
+ <child>
+ <object class="GtkButton" id="bubble-cancel-button">
+ <property name="name">bubble-cancel-button"</property>
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-save-button">
+ <property name="name">bubble-save-button"</property>
+ <property name="label" translatable="yes">Save</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">GTK_PACK_END</property>
+ </packing>
+ </child>
</object>
<packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
+ <property name="name">edit</property>
</packing>
+
+ </child>
+ </object>
+
+ <object class="GtkPopover" id="add-detail-popover">
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <child>
+ <object class="GtkButton" id="bubble-add-name-button">
+ <property name="visible">True</property>
+ <property name="sensitive">True</property>
+ <property name="label" translatable="true">Name</property>
+ <style>
+ <class name="menuitem"/>
+ </style>
+ <style>
+ <class name="button"/>
+ </style>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-add-wikipedia-button">
+ <property name="visible">True</property>
+ <property name="sensitive">True</property>
+ <property name="label" translatable="true">Wikipedia</property>
+ <style>
+ <class name="menuitem"/>
+ </style>
+ <style>
+ <class name="button"/>
+ </style>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-add-population-button">
+ <property name="visible">True</property>
+ <property name="sensitive">True</property>
+ <property name="label" translatable="true">Population</property>
+ <style>
+ <class name="menuitem"/>
+ </style>
+ <style>
+ <class name="button"/>
+ </style>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="bubble-add-wheelchair-button">
+ <property name="visible">True</property>
+ <property name="sensitive">True</property>
+ <property name="label" translatable="true">Wheelchair access</property>
+ <style>
+ <class name="menuitem"/>
+ </style>
+ <style>
+ <class name="button"/>
+ </style>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+
+ </object>
</child>
</object>
+ <object class="GtkSizeGroup" id="bubble-edit-label-size-group">
+ <property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property>
+ <widgets>
+ <widget name="bubble-edit-comment-label"/>
+ </widgets>
+ </object>
</interface>
diff --git a/data/ui/osm-edit-dialog.ui b/data/ui/osm-edit-dialog.ui
new file mode 100644
index 0000000..15170c3
--- /dev/null
+++ b/data/ui/osm-edit-dialog.ui
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="Gjs_OSMEditDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="type">popup</property>
+ <property name="type_hint">dialog</property>
+ <property name="width_request">500</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="contentArea">
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <property name="transition_type">crossfade</property>
+ <child>
+ <object class="GtkGrid" id="loadingGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinner" id="loadingSpinner">
+ <property name="height_request">32</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="active">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">loading</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="editorGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">15</property>
+ <property name="margin_end">15</property>
+ <property name="margin_top">15</property>
+ <property name="margin_bottom">15</property>
+ <child>
+ <object class="GtkLabel" id="nameLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="label" translatable="true">Name</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="nameEntry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">editor</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="uploadGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">15</property>
+ <property name="margin_end">15</property>
+ <property name="margin_top">15</property>
+ <property name="margin_bottom">15</property>
+ <child>
+ <object class="GtkLabel" id="commentLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="label" translatable="true">Comment:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="commentEntry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="sourceLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="label" translatable="true">Source:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="sourceEntry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">upload</property>
+ </packing>
+ </child>
+
+
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerBar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="show-close-button">False</property>
+ <child>
+ <object class="GtkButton" id="cancelButton">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="saveButton">
+ <property name="label" translatable="yes">_Save</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/lib/Makefile.am b/lib/Makefile.am
index cc4252c..1d7e9bf 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -9,12 +9,24 @@ libgnome_maps_headers_private = \
maps-contact.h \
mapsintl.h \
maps.h \
- maps-file-tile-source.h
+ maps-file-tile-source.h \
+ maps-osm.h \
+ maps-osm-changeset.h \
+ maps-osm-node.h \
+ maps-osm-object.h \
+ maps-osm-relation.h \
+ maps-osm-way.h
libgnome_maps_sources = \
maps-contact-store.c \
maps-contact.c \
- maps-file-tile-source.c
+ maps-file-tile-source.c \
+ maps-osm.c \
+ maps-osm-changeset.c \
+ maps-osm-node.c \
+ maps-osm-object.c \
+ maps-osm-way.c \
+ maps-osm-relation.c
libgnome_maps_la_SOURCES = \
$(libgnome_maps_sources) \
diff --git a/lib/maps-osm-changeset.c b/lib/maps-osm-changeset.c
new file mode 100644
index 0000000..719bcdc
--- /dev/null
+++ b/lib/maps-osm-changeset.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#include "maps-osm-changeset.h"
+
+G_DEFINE_TYPE (MapsOSMChangeset, maps_osm_changeset,
+ MAPS_TYPE_OSMOBJECT)
+
+static const char *
+maps_osm_changeset_get_xml_tag_name (void)
+{
+ return "changeset";
+}
+
+static void
+maps_osm_changeset_class_init (MapsOSMChangesetClass *klass)
+{
+ MapsOSMObjectClass *object_class = MAPS_OSMOBJECT_CLASS (klass);
+
+ object_class->get_xml_tag_name = maps_osm_changeset_get_xml_tag_name;
+}
+
+static void
+maps_osm_changeset_init (MapsOSMChangeset *changeset)
+{
+}
+
+MapsOSMChangeset *
+maps_osm_changeset_new (const char *comment, const char *source,
+ const char *created_by)
+{
+ MapsOSMChangeset *changeset = g_object_new (MAPS_TYPE_OSMCHANGESET, NULL);
+
+ if (comment)
+ maps_osm_object_set_tag (MAPS_OSMOBJECT (changeset), "comment", comment);
+
+ if (source)
+ maps_osm_object_set_tag (MAPS_OSMOBJECT (changeset), "source", source);
+
+ if (created_by)
+ maps_osm_object_set_tag (MAPS_OSMOBJECT (changeset), "created_by",
+ created_by);
+
+ return changeset;
+}
diff --git a/lib/maps-osm-changeset.h b/lib/maps-osm-changeset.h
new file mode 100644
index 0000000..51253ca
--- /dev/null
+++ b/lib/maps-osm-changeset.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#ifndef __MAPS_OSM_CHANGESET_H__
+#define __MAPS_OSM_CHANGESET_H__
+
+#include "maps-osm-object.h"
+
+#include <glib-object.h>
+
+#define MAPS_TYPE_OSMCHANGESET maps_osm_changeset_get_type ()
+G_DECLARE_FINAL_TYPE(MapsOSMChangeset, maps_osm_changeset, MAPS, OSMCHANGESET,
+ MapsOSMObject)
+
+struct _MapsOSMChangeset
+{
+ MapsOSMObject parent_instance;
+};
+
+struct _MapsOSMChangesetClass
+{
+ MapsOSMObjectClass parent_class;
+};
+
+/**
+ * maps_osm_changeset_new:
+ * @comment: (nullable): A comment about the OSM change, optional
+ * @source: (nullable): The source of the OSM change, optional
+ */
+MapsOSMChangeset *maps_osm_changeset_new (const char *comment,
+ const char *source,
+ const char *created_by);
+
+#endif /* __MAPS_OSM_CHANGESET_H__ */
+
diff --git a/lib/maps-osm-node.c b/lib/maps-osm-node.c
new file mode 100644
index 0000000..564acd3
--- /dev/null
+++ b/lib/maps-osm-node.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#include "maps-osm-node.h"
+
+struct _MapsOSMNodePrivate
+{
+ double lon;
+ double lat;
+};
+
+enum {
+ PROP_0,
+
+ PROP_LON,
+ PROP_LAT
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MapsOSMNode, maps_osm_node,
+ MAPS_TYPE_OSMOBJECT)
+
+
+static void
+maps_osm_node_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MapsOSMNode *node = MAPS_OSMNODE (object);
+
+ switch (property_id)
+ {
+ case PROP_LON:
+ node->priv->lon = g_value_get_double (value);
+ break;
+
+ case PROP_LAT:
+ node->priv->lat = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+maps_osm_node_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MapsOSMNode *node = MAPS_OSMNODE (object);
+
+ switch (property_id)
+ {
+ case PROP_LON:
+ g_value_set_double (value,
+ node->priv->lon);
+ break;
+
+ case PROP_LAT:
+ g_value_set_double (value,
+ node->priv->lat);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static const char *
+maps_osm_node_get_xml_tag_name (void)
+{
+ return "node";
+}
+
+static GHashTable *
+maps_osm_node_get_xml_attributes (const MapsOSMObject *object)
+{
+ const MapsOSMNode *node = MAPS_OSMNODE (object);
+ GHashTable *attributes;
+ char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, g_free);
+
+ g_ascii_dtostr (buf, sizeof (buf), node->priv->lon);
+ g_hash_table_insert (attributes, "lon", g_strdup (buf));
+ g_ascii_dtostr (buf, sizeof (buf), node->priv->lat);
+ g_hash_table_insert (attributes, "lat", g_strdup (buf));
+
+ return attributes;
+}
+
+static void
+maps_osm_node_class_init (MapsOSMNodeClass *klass)
+{
+ GObjectClass *node_class = G_OBJECT_CLASS (klass);
+ MapsOSMObjectClass *object_class = MAPS_OSMOBJECT_CLASS (klass);
+ GParamSpec *pspec;
+
+ node_class->get_property = maps_osm_node_get_property;
+ node_class->set_property = maps_osm_node_set_property;
+ object_class->get_xml_tag_name = maps_osm_node_get_xml_tag_name;
+ object_class->get_xml_attributes = maps_osm_node_get_xml_attributes;
+
+ /**
+ * MapsOSMNode:lon:
+ *
+ * The longitude of the node.
+ */
+ pspec = g_param_spec_double ("lon",
+ "Longitude",
+ "Longitude",
+ -180.0,
+ 180.0,
+ 0.0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (node_class, PROP_LON, pspec);
+
+ /**
+ * MapsOSMNode:lat:
+ *
+ * The latitude of the node.
+ */
+ pspec = g_param_spec_double ("lat",
+ "Latitude",
+ "Latitude",
+ -90.0,
+ 90.0,
+ 0.0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (node_class, PROP_LAT, pspec);
+}
+
+static void
+maps_osm_node_init (MapsOSMNode *node)
+{
+ node->priv = maps_osm_node_get_instance_private (node);
+
+ node->priv->lon = 0.0;
+ node->priv->lat = 0.0;
+}
+
+MapsOSMNode *
+maps_osm_node_new (guint64 id, guint version, guint64 changeset,
+ double lon, double lat)
+{
+ return g_object_new (MAPS_TYPE_OSMNODE,
+ "id", id,
+ "version", version,
+ "changeset", changeset,
+ "lon", lon,
+ "lat", lat, NULL);
+}
+
+double
+maps_osm_node_get_lon (MapsOSMNode *node)
+{
+ double lon;
+
+ g_object_get (node, "lon", &lon);
+ return lon;
+}
+
+void
+maps_osm_node_set_lon (MapsOSMNode *node, double lon)
+{
+ g_object_set (node, "lon", lon);
+}
+
+double
+maps_osm_node_get_lat (MapsOSMNode *node)
+{
+ double lat;
+
+ g_object_get (node, "lat", &lat);
+ return lat;
+}
+
+void
+maps_osm_node_set_lat (MapsOSMNode *node, double lat)
+{
+ g_object_set (node, "lat", lat);
+}
diff --git a/lib/maps-osm-node.h b/lib/maps-osm-node.h
new file mode 100644
index 0000000..a2a74d7
--- /dev/null
+++ b/lib/maps-osm-node.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#ifndef __MAPS_OSM_NODE_H__
+#define __MAPS_OSM_NODE_H__
+
+#include "maps-osm-object.h"
+
+#include <glib-object.h>
+
+#define MAPS_TYPE_OSMNODE maps_osm_node_get_type ()
+G_DECLARE_FINAL_TYPE(MapsOSMNode, maps_osm_node, MAPS, OSMNODE,
+ MapsOSMObject)
+
+typedef struct _MapsOSMNodePrivate MapsOSMNodePrivate;
+
+struct _MapsOSMNode
+{
+ MapsOSMObject parent_instance;
+ MapsOSMNodePrivate *priv;
+};
+
+struct _MapsOSMNodeClass
+{
+ MapsOSMObjectClass parent_class;
+};
+
+MapsOSMNode *maps_osm_node_new (guint64 id, guint version, guint64 changeset,
+ double lon, double lat);
+
+double maps_osm_node_get_lon (MapsOSMNode *node);
+void maps_osm_node_set_lon (MapsOSMNode *node, double lon);
+double maps_osm_node_get_lat (MapsOSMNode *node);
+void maps_osm_node_set_lat (MapsOSMNode *node, double lat);
+
+
+#endif //__MAPS_OSM_NODE_H__
diff --git a/lib/maps-osm-object.c b/lib/maps-osm-object.c
new file mode 100644
index 0000000..0d60e95
--- /dev/null
+++ b/lib/maps-osm-object.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#include "maps-osm-object.h"
+
+struct _MapsOSMObjectPrivate
+{
+ guint64 id;
+ guint version;
+ guint64 changeset;
+
+ GHashTable *tags;
+};
+
+enum {
+ PROP_0,
+
+ PROP_ID,
+ PROP_VERSION,
+ PROP_CHANGESET
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (MapsOSMObject, maps_osm_object,
+ G_TYPE_OBJECT)
+
+static void
+maps_osm_object_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MapsOSMObject *osm_object = MAPS_OSMOBJECT (object);
+ MapsOSMObjectPrivate *priv =
+ maps_osm_object_get_instance_private (osm_object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ priv->id = g_value_get_uint64 (value);
+ break;
+
+ case PROP_VERSION:
+ g_debug ("setting version: %d\n", g_value_get_uint (value));
+ priv->version = g_value_get_uint (value);
+ break;
+
+ case PROP_CHANGESET:
+ priv->changeset = g_value_get_uint64 (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+maps_osm_object_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MapsOSMObject *osm_object = MAPS_OSMOBJECT (object);
+ MapsOSMObjectPrivate *priv =
+ maps_osm_object_get_instance_private (osm_object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_uint64 (value,
+ priv->id);
+ break;
+
+ case PROP_VERSION:
+ g_value_set_uint (value,
+ priv->version);
+ g_debug ("getting version: %d\n", g_value_get_uint (value));
+ break;
+
+ case PROP_CHANGESET:
+ g_value_set_uint64 (value,
+ priv->changeset);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+maps_osm_object_dispose (GObject *object)
+{
+ MapsOSMObject *osm_object = MAPS_OSMOBJECT (object);
+ MapsOSMObjectPrivate *priv =
+ maps_osm_object_get_instance_private (osm_object);
+
+ g_hash_table_destroy (priv->tags);
+ priv->tags = NULL;
+
+ G_OBJECT_CLASS (maps_osm_object_parent_class)->dispose (object);
+}
+
+/* base implementation returning no object-specific XML attributes */
+static GHashTable *
+maps_osm_object_get_xml_attributes (const MapsOSMObject *object)
+{
+ return g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+/* base implementation return no object-specific child XML nodes */
+static xmlNodePtr
+maps_osm_object_get_xml_child_nodes (const MapsOSMObject *object)
+{
+ return NULL;
+}
+
+static void
+maps_osm_object_class_init (MapsOSMObjectClass *klass)
+{
+ GObjectClass *maps_class = G_OBJECT_CLASS (klass);
+ MapsOSMObjectClass *object_class = MAPS_OSMOBJECT_CLASS (klass);
+ GParamSpec *pspec;
+
+ maps_class->dispose = maps_osm_object_dispose;
+ maps_class->get_property = maps_osm_object_get_property;
+ maps_class->set_property = maps_osm_object_set_property;
+ object_class->get_xml_attributes = maps_osm_object_get_xml_attributes;
+ object_class->get_xml_child_nodes = maps_osm_object_get_xml_child_nodes;
+
+ /**
+ * MapsOSMObject:id:
+ *
+ * The OSM id of the object.
+ */
+ pspec = g_param_spec_uint64 ("id",
+ "ID",
+ "ID",
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (maps_class, PROP_ID, pspec);
+
+ /**
+ * MapsOSMObject:version:
+ *
+ * The latest OSM version of the object.
+ */
+ pspec = g_param_spec_uint("version",
+ "Version",
+ "Version",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (maps_class, PROP_VERSION, pspec);
+
+ /**
+ * MapsOSMObject:changeset:
+ *
+ * The OSM changeset for the current upload of the object.
+ */
+ pspec = g_param_spec_uint64 ("changeset",
+ "Changeset",
+ "Changeset",
+ 0,
+ G_MAXUINT64,
+ 0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (maps_class, PROP_CHANGESET, pspec);
+}
+
+static void
+maps_osm_object_init (MapsOSMObject *object)
+{
+ MapsOSMObjectPrivate *priv = maps_osm_object_get_instance_private (object);
+
+ priv->tags = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+}
+
+guint64
+maps_osm_object_get_id (MapsOSMObject *object)
+{
+ guint64 id;
+
+ g_object_get (G_OBJECT (object), "id", &id);
+ return id;
+}
+
+void
+maps_osm_object_set_id (MapsOSMObject *object, guint64 id)
+{
+ g_object_set (G_OBJECT (object), "id", id);
+}
+
+guint
+maps_osm_object_get_version (MapsOSMObject *object)
+{
+ guint version;
+
+ g_object_get (G_OBJECT (object), "version", &version);
+ return version;
+}
+
+void
+maps_osm_object_set_version (MapsOSMObject *object, guint version)
+{
+ g_object_set (G_OBJECT (object), "version", version);
+}
+
+guint64
+maps_osm_object_get_changeset (MapsOSMObject *object)
+{
+ guint64 changeset;
+
+ g_object_get (G_OBJECT (object), "changeset", &changeset);
+ return changeset;
+}
+
+void
+maps_osm_object_set_changset (MapsOSMObject *object, guint64 changeset)
+{
+ g_object_set (G_OBJECT (object), "changeset", changeset);
+}
+
+const char *
+maps_osm_object_get_tag (const MapsOSMObject *object, const char *key)
+{
+ MapsOSMObjectPrivate *priv = maps_osm_object_get_instance_private (object);
+
+ return g_hash_table_lookup (priv->tags, key);
+}
+
+void
+maps_osm_object_set_tag (MapsOSMObject *object,
+ const char *key, const char *value)
+{
+ MapsOSMObjectPrivate *priv = maps_osm_object_get_instance_private (object);
+
+ g_hash_table_insert (priv->tags, g_strdup (key), g_strdup (value));
+}
+
+void
+maps_osm_object_delete_tag (MapsOSMObject *object, const char *key)
+{
+ MapsOSMObjectPrivate *priv = maps_osm_object_get_instance_private (object);
+
+ g_hash_table_remove (priv->tags, key);
+}
+
+void
+maps_osm_object_foreach_tag (gpointer key, gpointer value, gpointer user_data)
+{
+ const char *name = (const char *) key;
+ const char *val = (const char *) value;
+ xmlNodePtr object_node = (xmlNodePtr) user_data;
+
+ /* skip tag if it has an empty placeholder value */
+ if (val) {
+ xmlNodePtr tag_node;
+
+ tag_node = xmlNewNode (NULL, "tag");
+ xmlNewProp (tag_node, "k", key);
+ xmlNewProp (tag_node, "v", val);
+ xmlAddChild (object_node, tag_node);
+ }
+}
+
+void
+maps_osm_object_foreach_type_attr (gpointer key, gpointer value,
+ gpointer user_data)
+{
+ const char *name = (const char *) key;
+ const char *val = (const char *) value;
+ xmlNodePtr object_node = (xmlNodePtr) user_data;
+
+ xmlNewProp (object_node, name, val);
+}
+
+xmlDocPtr
+maps_osm_object_to_xml (const MapsOSMObject *object)
+{
+ MapsOSMObjectPrivate *priv;
+ xmlDocPtr doc;
+ xmlNodePtr osm_node;
+ xmlNodePtr object_node;
+ const char *type;
+ guint64 id;
+ guint version;
+ guint64 changeset;
+ GHashTable *type_attrs;
+ xmlNodePtr type_sub_nodes;
+
+ doc = xmlNewDoc ("1.0");
+ osm_node = xmlNewNode (NULL, "osm");
+ priv = (MapsOSMObjectPrivate *) maps_osm_object_get_instance_private (object);
+ type = MAPS_OSMOBJECT_GET_CLASS (object)->get_xml_tag_name ();
+ object_node = xmlNewNode (NULL, type);
+
+ /* add common OSM attributes */
+ id = priv->id;
+ version = priv->version;
+ changeset = priv->changeset;
+
+ if (id != 0) {
+ char buf[32];
+ g_snprintf (buf, 32, "%" G_GUINT64_FORMAT, id);
+ xmlNewProp (object_node, "id", buf);
+ }
+
+ if (version != 0) {
+ char buf[16];
+ g_snprintf (buf, 16, "%d", version);
+ xmlNewProp (object_node, "version", buf);
+ }
+
+ if (changeset != 0) {
+ char buf[32];
+ g_snprintf (buf, 32, "%" G_GUINT64_FORMAT, changeset);
+ xmlNewProp (object_node, "changeset", buf);
+ }
+
+ /* add OSM tags */
+ g_hash_table_foreach (priv->tags, maps_osm_object_foreach_tag, object_node);
+
+ /* add type-specific attributes */
+ type_attrs = MAPS_OSMOBJECT_GET_CLASS (object)->get_xml_attributes (object);
+ g_hash_table_foreach (type_attrs, maps_osm_object_foreach_type_attr,
+ object_node);
+ g_hash_table_destroy (type_attrs);
+
+ /* add type-specific sub-nodes */
+ type_sub_nodes =
+ MAPS_OSMOBJECT_GET_CLASS (object)->get_xml_child_nodes (object);
+ if (type_sub_nodes)
+ xmlAddChildList (object_node, type_sub_nodes);
+
+ /* add type node to top node */
+ xmlAddChild (osm_node, object_node);
+ xmlDocSetRootElement (doc, osm_node);
+
+ return doc;
+}
+
+
+char *
+maps_osm_object_serialize (const MapsOSMObject *object)
+{
+ xmlDocPtr doc;
+ xmlChar *result;
+ int size;
+
+ doc = maps_osm_object_to_xml (object);
+ xmlDocDumpMemory (doc, &result, &size);
+ xmlFreeDoc (doc);
+
+ return result;
+}
diff --git a/lib/maps-osm-object.h b/lib/maps-osm-object.h
new file mode 100644
index 0000000..4ff3223
--- /dev/null
+++ b/lib/maps-osm-object.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#ifndef __MAPS_OSM_OBJECT_H__
+#define __MAPS_OSM_OBJECT_H__
+
+#include <glib-object.h>
+#include <libxml/xpath.h>
+
+#define MAPS_TYPE_OSMOBJECT maps_osm_object_get_type ()
+G_DECLARE_DERIVABLE_TYPE(MapsOSMObject, maps_osm_object, MAPS, OSMOBJECT,
+ GObject)
+
+typedef struct _MapsOSMObjectPrivate MapsOSMObjectPrivate;
+
+struct _MapsOSMObjectClass
+{
+ GObjectClass parent_class;
+
+ /* return the name of the distinguishing OSM XML tag (beneath <osm/>) */
+ const char * (* get_xml_tag_name) (void);
+
+ /* return hash table with XML attributes (key/values) specific for
+ the object (on the XML tag beneath <osm/>) */
+ GHashTable * (* get_xml_attributes) (const MapsOSMObject *object);
+
+ /* return a list of custom object-specific XML tags to attach,
+ can return NULL if there's no object-specific nodes */
+ xmlNodePtr (* get_xml_child_nodes) (const MapsOSMObject *object);
+};
+
+guint64 maps_osm_object_get_id (MapsOSMObject *object);
+void maps_osm_object_set_id (MapsOSMObject *object, guint64 id);
+guint maps_osm_object_get_version (MapsOSMObject *object);
+void maps_osm_object_set_version (MapsOSMObject *object, guint version);
+guint64 maps_osm_object_get_changeset (MapsOSMObject *object);
+void maps_osm_object_set_changeset (MapsOSMObject *object, guint64 changeset);
+
+const char *maps_osm_object_get_tag (const MapsOSMObject *object,
+ const char *key);
+void maps_osm_object_set_tag (MapsOSMObject *object, const char *key,
+ const char *value);
+void maps_osm_object_delete_tag (MapsOSMObject *object, const char *key);
+
+char *maps_osm_object_serialize (const MapsOSMObject *object);
+
+#endif //__MAPS_OSM_OBJECT_H__
diff --git a/lib/maps-osm-relation.c b/lib/maps-osm-relation.c
new file mode 100644
index 0000000..bc866e6
--- /dev/null
+++ b/lib/maps-osm-relation.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#include "maps-osm-relation.h"
+
+struct _MapsOSMRelationPrivate
+{
+ GList *members;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MapsOSMRelation, maps_osm_relation,
+ MAPS_TYPE_OSMOBJECT);
+
+typedef struct
+{
+ char *role;
+ guint type;
+ guint64 ref;
+} MapsOSMRelationMember;
+
+static void
+maps_osm_relation_member_free (gpointer data)
+{
+ MapsOSMRelationMember *member = (MapsOSMRelationMember *) data;
+
+ g_free (member->role);
+}
+
+static void
+maps_osm_relation_dispose (GObject *object)
+{
+ MapsOSMRelation *relation = MAPS_OSMRELATION (object);
+
+ g_list_free_full (relation->priv->members, maps_osm_relation_member_free);
+ relation->priv->members = NULL;
+
+ G_OBJECT_CLASS (maps_osm_relation_parent_class)->dispose (object);
+}
+
+static const char *
+maps_osm_relation_get_xml_tag_name (void)
+{
+ return "relation";
+}
+
+static const char *
+maps_osm_relation_member_type_to_string (guint type)
+{
+ switch (type) {
+ case MEMBER_TYPE_NODE:
+ return "node";
+ case MEMBER_TYPE_WAY:
+ return "way";
+ case MEMBER_TYPE_RELATION:
+ return "relation";
+ default:
+ g_warning ("Unknown relation member type: %d\n", type);
+ return NULL;
+ }
+}
+
+static xmlNodePtr
+maps_osm_relation_get_member_node (const MapsOSMRelationMember *member)
+{
+ xmlNodePtr node = xmlNewNode (NULL, "member");
+ char buf[16];
+
+ if (member->role)
+ xmlNewProp (node, "role", g_strdup (member->role));
+
+ xmlNewProp (node, "type",
+ maps_osm_relation_member_type_to_string (member->type));
+ g_snprintf (buf, 16, "%" G_GUINT64_FORMAT, member->ref);
+ xmlNewProp (node, "ref", buf);
+
+ return node;
+}
+
+static xmlNodePtr
+maps_osm_relation_get_xml_child_nodes (const MapsOSMObject *object)
+{
+ MapsOSMRelation *relation = MAPS_OSMRELATION (object);
+ xmlNodePtr nodes = NULL;
+ const GList *members = relation->priv->members;
+
+ if (members) {
+ const GList *iter;
+ nodes = maps_osm_relation_get_member_node ((MapsOSMRelationMember *)
+ members->data);
+
+ for (iter = members->next; iter; iter = iter->next) {
+ xmlAddSibling (nodes,
+ maps_osm_relation_get_member_node (
+ (MapsOSMRelationMember *) iter->data));
+ }
+ }
+
+ return nodes;
+}
+
+static void
+maps_osm_relation_class_init (MapsOSMRelationClass *klass)
+{
+ GObjectClass *relation_class = G_OBJECT_CLASS (klass);
+ MapsOSMObjectClass *object_class = MAPS_OSMOBJECT_CLASS (klass);
+
+ relation_class->dispose = maps_osm_relation_dispose;
+ object_class->get_xml_tag_name = maps_osm_relation_get_xml_tag_name;
+ object_class->get_xml_child_nodes = maps_osm_relation_get_xml_child_nodes;
+}
+
+static void
+maps_osm_relation_init (MapsOSMRelation *relation)
+{
+ relation->priv = maps_osm_relation_get_instance_private (relation);
+}
+
+MapsOSMRelation *
+maps_osm_relation_new (guint64 id, guint version, guint64 changeset)
+{
+ return g_object_new (MAPS_TYPE_OSMRELATION,
+ "id", id,
+ "version", version,
+ "changeset", changeset, NULL);
+}
+
+void
+maps_osm_relation_add_member (MapsOSMRelation *relation,
+ const gchar *role, guint type, guint64 ref)
+{
+ MapsOSMRelationMember *member = g_new (MapsOSMRelationMember, 1);
+
+ member->role = g_strdup (role);
+ member->type = type;
+ member->ref = ref;
+
+ relation->priv->members = g_list_append (relation->priv->members,
+ member);
+}
+
+
diff --git a/lib/maps-osm-relation.h b/lib/maps-osm-relation.h
new file mode 100644
index 0000000..9e0119d
--- /dev/null
+++ b/lib/maps-osm-relation.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#ifndef __MAPS_OSM_RELATION_H__
+#define __MAPS_OSM_RELATION_H__
+
+#include "maps-osm-object.h"
+
+#include <glib-object.h>
+
+#define MAPS_TYPE_OSMRELATION maps_osm_relation_get_type ()
+G_DECLARE_FINAL_TYPE(MapsOSMRelation, maps_osm_relation, MAPS, OSMRELATION,
+ MapsOSMObject)
+
+typedef struct _MapsOSMRelationPrivate MapsOSMRelationPrivate;
+
+struct _MapsOSMRelation
+{
+ MapsOSMObject parent_instance;
+ MapsOSMRelationPrivate *priv;
+};
+
+struct _MapsOSMRelationClass
+{
+ MapsOSMObjectClass parent_class;
+};
+
+enum {
+ MEMBER_TYPE_NODE,
+ MEMBER_TYPE_WAY,
+ MEMBER_TYPE_RELATION
+};
+
+MapsOSMRelation *maps_osm_relation_new (guint64 id, guint version,
+ guint64 changeset);
+
+void maps_osm_relation_add_member (MapsOSMRelation *relation,
+ const char *role, guint type,
+ guint64 ref);
+
+#endif /* __MAPS_OSM_RELATION_H__ */
+
diff --git a/lib/maps-osm-way.c b/lib/maps-osm-way.c
new file mode 100644
index 0000000..841bad6
--- /dev/null
+++ b/lib/maps-osm-way.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#include "maps-osm-way.h"
+
+struct _MapsOSMWayPrivate
+{
+ GArray *node_ids;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MapsOSMWay, maps_osm_way,
+ MAPS_TYPE_OSMOBJECT);
+
+static void
+maps_osm_way_dispose (GObject *object)
+{
+ MapsOSMWay *way = MAPS_OSMWAY (object);
+
+ g_array_free (way->priv->node_ids, TRUE);
+ way->priv->node_ids = NULL;
+
+ G_OBJECT_CLASS (maps_osm_way_parent_class)->dispose (object);
+}
+
+static const char *
+maps_osm_way_get_xml_tag_name (void)
+{
+ return "way";
+}
+
+static xmlNodePtr
+maps_osm_way_create_node_xml_node (guint64 ref)
+{
+ xmlNodePtr nd;
+ char buf[16];
+
+ g_snprintf (buf, 16, "%" G_GUINT64_FORMAT, ref);
+ nd = xmlNewNode (NULL, "nd");
+ xmlNewProp (nd, "ref", buf);
+
+ return nd;
+}
+
+static xmlNodePtr
+maps_osm_way_get_xml_child_nodes(const MapsOSMObject *object)
+{
+ const MapsOSMWay *way = MAPS_OSMWAY (object);
+ int i;
+ xmlNodePtr result;
+ xmlNodePtr next;
+
+ g_return_if_fail (way->priv->node_ids->len > 0);
+
+ result = maps_osm_way_create_node_xml_node (g_array_index (way->priv->node_ids,
+ guint64, 0));
+ next = result;
+
+ for (i = 1; i < way->priv->node_ids->len; i++) {
+ xmlNodePtr new_node;
+ new_node =
+ maps_osm_way_create_node_xml_node (g_array_index (way->priv->node_ids,
+ guint64, i));
+ next = xmlAddNextSibling (next, new_node);
+ }
+
+ return result;
+}
+
+static void
+maps_osm_way_class_init (MapsOSMWayClass *klass)
+{
+ GObjectClass *way_class = G_OBJECT_CLASS (klass);
+ MapsOSMObjectClass *object_class = MAPS_OSMOBJECT_CLASS (klass);
+
+ way_class->dispose = maps_osm_way_dispose;
+ object_class->get_xml_tag_name = maps_osm_way_get_xml_tag_name;
+ object_class->get_xml_child_nodes = maps_osm_way_get_xml_child_nodes;
+}
+
+static void
+maps_osm_way_init (MapsOSMWay *way)
+{
+ way->priv = maps_osm_way_get_instance_private (way);
+ way->priv->node_ids = g_array_new (FALSE, FALSE, sizeof (guint64));
+}
+
+MapsOSMWay *
+maps_osm_way_new (guint64 id, guint version, guint64 changeset)
+{
+ return g_object_new (MAPS_TYPE_OSMWAY,
+ "id", id,
+ "version", version,
+ "changeset", changeset, NULL);
+}
+
+void
+maps_osm_way_add_node_id (MapsOSMWay *way, guint64 id)
+{
+ g_array_append_val (way->priv->node_ids, id);
+}
diff --git a/lib/maps-osm-way.h b/lib/maps-osm-way.h
new file mode 100644
index 0000000..2817d3e
--- /dev/null
+++ b/lib/maps-osm-way.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#ifndef __MAPS_OSM_WAY_H__
+#define __MAPS_OSM_WAY_H__
+
+#include "maps-osm-object.h"
+
+#include <glib-object.h>
+
+#define MAPS_TYPE_OSMWAY maps_osm_way_get_type ()
+G_DECLARE_FINAL_TYPE(MapsOSMWay, maps_osm_way, MAPS, OSMWAY,
+ MapsOSMObject)
+
+typedef struct _MapsOSMWayPrivate MapsOSMWayPrivate;
+
+struct _MapsOSMWay
+{
+ MapsOSMObject parent_instance;
+ MapsOSMWayPrivate *priv;
+};
+
+struct _MapsOSMWayClass
+{
+ MapsOSMObjectClass parent_class;
+};
+
+MapsOSMWay *maps_osm_way_new (guint64 id, guint version, guint64 changeset);
+
+void maps_osm_way_add_node_id (MapsOSMWay *way, guint64 id);
+
+#endif /* __MAPS_OSM_WAY_H__ */
+
diff --git a/lib/maps-osm.c b/lib/maps-osm.c
new file mode 100644
index 0000000..86dcca6
--- /dev/null
+++ b/lib/maps-osm.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#include "maps-osm.h"
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+void
+maps_osm_init (void)
+{
+ LIBXML_TEST_VERSION;
+}
+
+void
+maps_osm_finalize (void)
+{
+ xmlCleanupParser ();
+}
+
+static xmlDocPtr
+read_xml_doc (const char *content, guint length)
+{
+ xmlDoc *doc;
+
+ doc = xmlReadMemory (content, length, "noname.xml", NULL, 0);
+
+ if (!doc) {
+ g_error ("Failed to parse to XML document");
+ return NULL;
+ }
+
+ return doc;
+}
+
+static void
+parse_tag (const xmlAttr *attrs, GHashTable *tags)
+{
+ const xmlAttr *cur_attr;
+ char *key;
+ char *value;
+ char *result;
+
+ key = NULL;
+ value = NULL;
+
+ for (cur_attr = attrs; cur_attr; cur_attr = cur_attr->next) {
+ if (g_str_equal (cur_attr->name, "k")) {
+ key = cur_attr->children->content, "";
+ } else if (g_str_equal (cur_attr->name, "v")) {
+ value = cur_attr->children->content, "";
+ } else {
+ g_warning ("Unexpected tag property: %s\n", cur_attr->name);
+ }
+ }
+
+ g_hash_table_insert (tags, key, value);
+}
+
+static GHashTable *
+parse_attributes (const xmlNode *node)
+{
+ GHashTable *attributes;
+ const xmlAttr *cur_attr;
+
+ attributes = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) {
+ g_hash_table_insert (attributes,
+ (gpointer) cur_attr->name,
+ (gpointer) cur_attr->children->content);
+ }
+
+ return attributes;
+}
+
+static GHashTable *
+parse_tags (const xmlNode *tag_child)
+{
+ GHashTable *tags;
+ const xmlNode *cur_node;
+
+ tags = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (cur_node = tag_child; cur_node; cur_node = cur_node->next) {
+ /* skip non-element nodes */
+ if (cur_node->type != XML_ELEMENT_NODE) {
+ continue;
+ }
+
+ if (g_str_equal (cur_node->name, "tag")) {
+ char *tag;
+
+ parse_tag (cur_node->properties, tags);
+ }
+ }
+
+ return tags;
+}
+
+static GArray *
+parse_node_refs (const xmlNode *node_ref_child)
+{
+ GArray *node_refs;
+ const xmlNode *cur_node;
+
+ node_refs = g_array_new (FALSE, FALSE, sizeof (guint64));
+
+ for (cur_node = node_ref_child; cur_node; cur_node = cur_node->next) {
+ /* skip non-element nodes */
+ if (cur_node->type != XML_ELEMENT_NODE) {
+ continue;
+ }
+
+ if (g_str_equal (cur_node->name, "nd")) {
+ char *ref;
+ GHashTable *attributes;
+
+ attributes = parse_attributes (cur_node);
+ ref = g_hash_table_lookup (attributes, "ref");
+
+ if (ref) {
+ guint64 id = g_ascii_strtoull (ref, NULL, 10);
+
+ if (id == 0)
+ g_warning ("Invalid node ref: %s", ref);
+ else
+ g_array_append_val (node_refs, id);
+ }
+
+ g_hash_table_destroy (attributes);
+ }
+ }
+
+ return node_refs;
+}
+
+static xmlNode *
+get_sub_node (xmlDoc *doc, const char *name)
+{
+ xmlNode *node;
+ xmlXPathContext *xpath_ctx;
+ xmlXPathObject * xpath_obj;
+ char *xpath;
+
+ xpath = g_strdup_printf ("/osm/%s", name);
+ xpath_ctx = xmlXPathNewContext (doc);
+ xpath_obj = xmlXPathEvalExpression (xpath, xpath_ctx);
+
+ if (xpath_obj && xpath_obj->nodesetval && xpath_obj->nodesetval->nodeNr > 0) {
+ node = xmlCopyNode (xpath_obj->nodesetval->nodeTab[0], 1);
+ } else {
+ g_warning ("Couldn't find element %s\n", name);
+ node = NULL;
+ }
+
+ xmlXPathFreeObject (xpath_obj);
+ xmlXPathFreeContext (xpath_ctx);
+ g_free (xpath);
+
+ return node;
+}
+
+static void
+for_each_tag (gpointer key, gpointer value, gpointer user_data)
+{
+ const char *k = (const char *) key;
+ const char *v = (const char *) value;
+ MapsOSMObject *object = MAPS_OSMOBJECT (user_data);
+
+ maps_osm_object_set_tag (object, k, v);
+}
+
+static void
+fill_tags (MapsOSMObject *object, GHashTable *tags)
+{
+ g_hash_table_foreach (tags, for_each_tag, object);
+}
+
+static void
+fill_node_ref_list (MapsOSMWay *way, const GArray *node_refs)
+{
+ int i;
+
+ for (i = 0; i < node_refs->len; i++) {
+ maps_osm_way_add_node_id (way, g_array_index (node_refs, guint64, i));
+ }
+}
+
+/**
+ * maps_osm_parse_node:
+ * @content: XML data
+ * @length: Length of data
+ * Returns: (transfer full): An OSMNode
+ */
+MapsOSMNode *
+maps_osm_parse_node (const char *content, guint length)
+{
+ xmlDoc *doc;
+ xmlNode *node;
+
+ const char *id_string;
+ guint64 id;
+ const char *changeset_string;
+ guint64 changeset;
+ const char *version_string;
+ guint version;
+ const char *lat_string;
+ double lat;
+ const char *lon_string;
+ double lon;
+
+ const xmlAttr *cur_attr;
+
+ GHashTable *tags;
+ GHashTable *attributes;
+
+ MapsOSMNode *result;
+
+ doc = read_xml_doc (content, length);
+
+ if (!doc) {
+ return NULL;
+ }
+
+ node = get_sub_node (doc, "node");
+
+ if (!node) {
+ g_error ("Missing <node/> element");
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ attributes = parse_attributes (node);
+
+ id_string = g_hash_table_lookup (attributes, "id");
+ changeset_string = g_hash_table_lookup (attributes, "changeset");
+ version_string = g_hash_table_lookup (attributes, "version");
+ lat_string = g_hash_table_lookup (attributes, "lat");
+ lon_string = g_hash_table_lookup (attributes, "lon");
+
+ if (!id_string || !changeset_string || !version_string
+ || !lat_string || !lon_string) {
+ g_warning ("Missing required attributes\n");
+ xmlFreeDoc (doc);
+ xmlFreeNode (node);
+ g_hash_table_destroy (attributes);
+ return NULL;
+ }
+
+ id = g_ascii_strtoull (id_string, NULL, 10);
+ changeset = g_ascii_strtoull (changeset_string, NULL, 10);
+ version = g_ascii_strtoull (version_string, NULL, 10);
+ lon = g_ascii_strtod (lon_string, NULL);
+ lat = g_ascii_strtod (lat_string, NULL);
+
+ g_hash_table_destroy (attributes);
+
+ result = maps_osm_node_new (id, version, changeset, lon, lat);
+
+ tags = parse_tags (node->children);
+ fill_tags (MAPS_OSMOBJECT (result), tags);
+
+ g_hash_table_destroy (tags);
+
+ xmlFreeDoc (doc);
+ xmlFreeNode (node);
+
+ return result;
+}
+
+/**
+ * maps_osm_parse_way:
+ * @content: XML data
+ * @length: Length of data
+ * Returns: (transfer full): An OSMWay
+ */
+MapsOSMWay *
+maps_osm_parse_way (const char *content, guint length)
+{
+ xmlDoc *doc;
+ xmlNode *way;
+ GHashTable *attributes;
+ GHashTable *tags;
+ GArray *node_refs;
+ MapsOSMWay *result;
+
+ const char *id_string;
+ guint64 id;
+ const char *changeset_string;
+ guint64 changeset;
+ const char *version_string;
+ guint version;
+
+ doc = read_xml_doc (content, length);
+
+ if (!doc) {
+ return NULL;
+ }
+
+ way = get_sub_node (doc, "way");
+
+ if (!way) {
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ attributes = parse_attributes (way);
+
+ id_string = g_hash_table_lookup (attributes, "id");
+ changeset_string = g_hash_table_lookup (attributes, "changeset");
+ version_string = g_hash_table_lookup (attributes, "version");
+
+ if (!id_string || !changeset_string || !version_string) {
+ g_warning ("Missing required attributes\n");
+ xmlFreeDoc (doc);
+ xmlFreeNode (way);
+ g_hash_table_destroy (attributes);
+ return NULL;
+ }
+
+ g_hash_table_destroy (attributes);
+
+ id = g_ascii_strtoull (id_string, NULL, 10);
+ changeset = g_ascii_strtoull (changeset_string, NULL, 10);
+ version = g_ascii_strtoull (version_string, NULL, 10);
+
+ result = maps_osm_way_new (id, version, changeset);
+
+ tags = parse_tags (way->children);
+ fill_tags (MAPS_OSMOBJECT (result), tags);
+ g_hash_table_destroy (tags);
+
+ node_refs = parse_node_refs (way->children);
+ fill_node_ref_list (result, node_refs);
+ g_array_free (node_refs, TRUE);
+
+ xmlFreeDoc (doc);
+ xmlFreeNode (way);
+
+ return result;
+}
+
+
+static GList *
+parse_members (const xmlNode *member_child)
+{
+ const xmlNode *cur_node;
+ GList *members;
+
+ members = NULL;
+
+ for (cur_node = member_child; cur_node; cur_node = cur_node->next) {
+ /* skip non-element nodes */
+ if (cur_node->type != XML_ELEMENT_NODE) {
+ continue;
+ }
+
+ if (g_str_equal (cur_node->name, "member")) {
+ GHashTable *attributes;
+
+ attributes = parse_attributes (cur_node);
+ members = g_list_append (members, attributes);
+ }
+ }
+
+ return members;
+}
+
+static void
+fill_members (MapsOSMRelation *relation, const GList *members)
+{
+ const GList *cur;
+
+ for (cur = members; cur; cur = g_list_next (cur)) {
+ GHashTable *attributes = (GHashTable *) cur->data;
+ const char *type_string = g_hash_table_lookup (attributes, "type");
+ guint type;
+ const char *role = g_hash_table_lookup (attributes, "role");
+ const char *ref_string = g_hash_table_lookup (attributes, "ref");
+ guint64 ref;
+
+ if (ref_string)
+ ref = g_ascii_strtoull (ref_string, NULL, 10);
+
+ if (g_strcmp0 (type_string, "node") == 0) {
+ type = MEMBER_TYPE_NODE;
+ } else if (g_strcmp0 (type_string, "way") == 0) {
+ type = MEMBER_TYPE_WAY;
+ } else if (g_strcmp0 (type_string, "relation") == 0) {
+ type = MEMBER_TYPE_RELATION;
+ } else {
+ g_warning ("Unknown relation type: %s\n", type_string);
+ continue;
+ }
+
+ maps_osm_relation_add_member (relation, role, type, ref);
+ }
+}
+
+/**
+ * maps_osm_parse_relation:
+ * @content: XML data
+ * @length: Length of data
+ * Returns: (transfer full): An OSMRelation
+ */
+MapsOSMRelation *
+maps_osm_parse_relation (const char *content, guint length)
+{
+ xmlDoc *doc;
+ xmlNode *relation;
+ GHashTable *attributes;
+ GHashTable *tags;
+ GList *member_list;
+
+ const char *id_string;
+ guint64 id;
+ const char *changeset_string;
+ guint64 changeset;
+ const char *version_string;
+ guint version;
+
+ MapsOSMRelation *result;
+
+ doc = read_xml_doc (content, length);
+
+ if (!doc) {
+ return NULL;
+ }
+
+ relation = get_sub_node (doc, "relation");
+
+ if (!relation) {
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ attributes = parse_attributes (relation);
+ id_string = g_hash_table_lookup (attributes, "id");
+ changeset_string = g_hash_table_lookup (attributes, "changeset");
+ version_string = g_hash_table_lookup (attributes, "version");
+
+ if (!id_string || !changeset_string || !version_string) {
+ g_error ("Missing required attributes\n");
+ xmlFreeDoc (doc);
+ xmlFreeNode (relation);
+ g_hash_table_destroy (attributes);
+ return NULL;
+ }
+
+ g_hash_table_destroy (attributes);
+
+ id = g_ascii_strtoull (id_string, NULL, 10);
+ changeset = g_ascii_strtoull (changeset_string, NULL, 10);
+ version = g_ascii_strtoull (version_string, NULL, 10);
+
+ result = maps_osm_relation_new (id, version, changeset);
+
+ tags = parse_tags (relation->children);
+ fill_tags (MAPS_OSMOBJECT (result), tags);
+ g_hash_table_destroy (tags);
+
+ member_list = parse_members (relation->children);
+ fill_members (result, member_list);
+ g_list_free_full (member_list, (GDestroyNotify) g_hash_table_destroy);
+
+ xmlFreeDoc (doc);
+ xmlFreeNode (relation);
+
+ return result;
+}
diff --git a/lib/maps-osm.h b/lib/maps-osm.h
new file mode 100644
index 0000000..d9b84bb
--- /dev/null
+++ b/lib/maps-osm.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+#ifndef __MAPS_OSM_H__
+#define __MAPS_OSM_H__
+
+#include <glib.h>
+
+#include "maps-osm-node.h"
+#include "maps-osm-way.h"
+#include "maps-osm-relation.h"
+
+void maps_osm_init (void);
+void maps_osm_finalize (void);
+
+MapsOSMNode *maps_osm_parse_node (const char *content, guint length);
+MapsOSMWay *maps_osm_parse_way (const char *content, guint length);
+MapsOSMRelation *maps_osm_parse_relation (const char *content, guint length);
+
+#endif
diff --git a/src/application.js b/src/application.js
index e92e6e8..548363b 100644
--- a/src/application.js
+++ b/src/application.js
@@ -35,6 +35,7 @@ const GeocodeService = imports.geocodeService;
const MainWindow = imports.mainWindow;
const Maps = imports.gi.GnomeMaps;
const NotificationManager = imports.notificationManager;
+const OSMEdit = imports.osmEdit;
const PlaceStore = imports.placeStore;
const RouteService = imports.routeService;
const Settings = imports.settings;
@@ -51,6 +52,7 @@ let geocodeService = null;
let networkMonitor = null;
let checkInManager = null;
let contactStore = null;
+let osmEditManager = null;
const Application = new Lang.Class({
Name: 'Application',
@@ -218,6 +220,7 @@ const Application = new Lang.Class({
checkInManager = new CheckIn.CheckInManager();
contactStore = new Maps.ContactStore();
contactStore.load();
+ osmEditManager = new OSMEdit.OSMEditManager();
},
_createWindow: function() {
diff --git a/src/mapBubble.js b/src/mapBubble.js
index 0d67414..a29aab1 100644
--- a/src/mapBubble.js
+++ b/src/mapBubble.js
@@ -19,7 +19,6 @@
* Author: Damián Nohales <damiannohales gmail com>
*/
-const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
@@ -37,7 +36,8 @@ const Button = {
ROUTE: 2,
SEND_TO: 4,
FAVORITE: 8,
- CHECK_IN: 16
+ CHECK_IN: 16,
+ EDIT: 32
};
const MapBubble = new Lang.Class({
@@ -68,17 +68,44 @@ const MapBubble = new Lang.Class({
params.modal = false;
this.parent(params);
- let ui = Utils.getUIObject('map-bubble', [ 'bubble-main-grid',
+ let ui = Utils.getUIObject('map-bubble', [ 'stack',
+ 'bubble-main-grid',
+ 'bubble-edit-box',
+ 'bubble-edit-content-area',
'bubble-image',
'bubble-content-area',
'bubble-button-area',
'bubble-route-button',
'bubble-send-to-button',
'bubble-favorite-button',
- 'bubble-check-in-button']);
+ 'bubble-check-in-button',
+ 'bubble-edit-button',
+ 'bubble-add-detail-button',
+ 'bubble-save-button',
+ 'bubble-cancel-button',
+ 'bubble-add-name-button',
+ 'bubble-add-wikipedia-button',
+ 'bubble-add-population-button',
+ 'bubble-add-wheelchair-button',
+ 'bubble-edit-comment-entry',
+ 'bubble-edit-label-size-group']);
+ this._viewOrEditStack = ui.stack;
+ this._mainGrid = ui.bubbleMainGrid;
+ this._editBox = ui.bubbleEditBox;
+ this._editContentArea = ui.bubbleEditContentArea;
this._image = ui.bubbleImage;
this._content = ui.bubbleContentArea;
-
+ this._editButton = ui.bubbleEditButton;
+ this._saveButton = ui.bubbleSaveButton;
+ this._cancelButton = ui.bubbleCancelButton;
+ this._addDetailButton = ui.bubbleAddDetailButton;
+ this._addNameButton = ui.bubbleAddNameButton;
+ this._addWikipediaButton = ui.bubbleAddWikipediaButton;
+ this._addPopulationButton = ui.bubbleAddPopulationButton;
+ this._addWheelchairButton = ui.bubbleAddWheelchairButton;
+ this._editCommentEntry = ui.bubbleEditCommentEntry;
+ this._editLabelSizeGroup = ui.bubbleEditLabelSizeGroup;
+
if (!buttonFlags)
ui.bubbleButtonArea.visible = false;
else {
@@ -90,9 +117,9 @@ const MapBubble = new Lang.Class({
this._initFavoriteButton(ui.bubbleFavoriteButton);
if (buttonFlags & Button.CHECK_IN)
this._initCheckInButton(ui.bubbleCheckInButton, checkInMatchPlace);
- }
+ }
- this.add(ui.bubbleMainGrid);
+ this.add(ui.stack);
},
get image() {
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index a15ac7e..dc02a83 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -27,6 +27,10 @@
<file>mapWalker.js</file>
<file>notification.js</file>
<file>notificationManager.js</file>
+ <file>osmConnection.js</file>
+ <file>osmEdit.js</file>
+ <file>osmEditDialog.js</file>
+ <file>osmUtils.js</file>
<file>overpass.js</file>
<file>place.js</file>
<file>placeBubble.js</file>
diff --git a/src/osmConnection.js b/src/osmConnection.js
new file mode 100644
index 0000000..a25e7cb
--- /dev/null
+++ b/src/osmConnection.js
@@ -0,0 +1,254 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const _ = imports.gettext.gettext;
+
+const Utils = imports.utils;
+
+const Lang = imports.lang;
+const GLib = imports.gi.GLib;
+const Maps = imports.gi.GnomeMaps;
+const Soup = imports.gi.Soup;
+
+const BASE_URL = 'https://api.openstreetmap.org/api';
+const TEST_BASE_URL = 'http://api06.dev.openstreetmap.org/api';
+const API_VERSION = '0.6';
+
+const OSMConnection = new Lang.Class({
+ Name: 'OSMConnection',
+
+ _init: function(params) {
+ this._session = new Soup.Session();
+
+ // TODO: stopgap to supply username/password
+ // to use with HTTP basic auth, should be
+ // replaced with OAUTH and real settings or GOA account
+ this._username = GLib.getenv('OSM_USERNAME');
+ this._password = GLib.getenv('OSM_PASSWORD');
+
+ let useLiveApi = GLib.getenv('OSM_USE_LIVE_API');
+
+ // for now use the test API unless explicitly
+ // set to use the live one
+ if (useLiveApi == 'yes')
+ this._useTestApi = false;
+ else
+ this._useTestApi = true;
+
+ this._session.connect('authenticate', this._authenticate.bind(this));
+
+ Maps.osm_init();
+ },
+
+ get useTestAPI() {
+ return this._useTestApi;
+ },
+
+ getOSMObject: function(type, id, callback, cancellable) {
+ let url = this._getQueryUrl(type, id);
+ let uri = new Soup.URI(url);
+ let request = new Soup.Message({ method: 'GET',
+ uri: uri });
+
+ cancellable.connect((function() {
+ this._session.cancel_message(request, Soup.STATUS_CANCELLED);
+ }).bind(this));
+
+ this._session.queue_message(request, (function(obj, message) {
+ if (message.status_code !== Soup.Status.OK) {
+ callback(false, message.status_code, null);
+ return;
+ }
+
+ Utils.debug ('data received: ' + message.response_body.data);
+
+ // override object type to use the mock object if using the test API
+ if (this._useTestApi)
+ type = GLib.getenv('OSM_MOCK_TYPE');
+
+ let object = this._parseXML(type, message.response_body);
+
+ if (object == null)
+ callback(false, message.status_code, null, type);
+ else
+ callback(true,
+ message.status_code,
+ object, type);
+ }).bind(this));
+ },
+
+ _getQueryUrl: function(type, id) {
+ if (this._useTestApi) {
+ // override object type and ID from a mock object
+ // since the object we get from Nominatim and Overpass
+ // doesn't exist in the test OSM environment
+ type = GLib.getenv('OSM_MOCK_TYPE');
+ id = GLib.getenv('OSM_MOCK_ID');
+ }
+
+ return this._getBaseUrl() + '/' + API_VERSION + '/' + type + '/' + id;
+ },
+
+ _getBaseUrl: function() {
+ return this._useTestApi ? TEST_BASE_URL : BASE_URL;
+ },
+
+ _parseXML: function(type, body) {
+ let object;
+
+ switch (type) {
+ case 'node':
+ object = Maps.osm_parse_node(body.data, body.length);
+ break;
+ case 'way':
+ object = Maps.osm_parse_way(body.data, body.length);
+ break;
+ case 'relation':
+ object = Maps.osm_parse_relation(body.data, body.length);
+ break;
+ default:
+ GLib.error('unknown OSM type: ' + type);
+ }
+
+ return object;
+ },
+
+ openChangeset: function(comment, source, callback) {
+ let changeset =
+ Maps.OSMChangeset.new(comment, source, 'gnome-maps ' + pkg.version);
+ let xml = changeset.serialize();
+
+ Utils.debug('about open changeset:\n' + xml + '\n');
+
+ let url = this._getOpenChangesetUrl();
+ let uri = new Soup.URI(url);
+ let msg = new Soup.Message({ method: 'PUT',
+ uri: uri });
+ msg.set_request('text/xml', Soup.MemoryUse.COPY, xml, xml.length);
+
+ this._session.queue_message(msg, (function(obj, message) {
+ if (message.status_code !== Soup.Status.OK) {
+ callback(false, message.status_code, null);
+ return;
+ }
+
+ let changesetId = GLib.ascii_strtoull (message.response_body.data,
+ '', 10);
+ callback(true, message.status_code, changesetId);
+
+ }));
+ },
+
+ uploadObject: function(object, type, changeset, callback) {
+ object.changeset = changeset;
+
+ let xml = object.serialize();
+
+ Utils.debug('about to upload object:\n' + xml + '\n');
+
+ let url = this._getCreateOrUpdateUrl(object, type);
+ let uri = new Soup.URI(url);
+ let msg = new Soup.Message({ method: 'PUT',
+ uri: uri });
+ msg.set_request('text/xml', Soup.MemoryUse.COPY, xml, xml.length);
+
+ this._session.queue_message(msg, (function(obj, message) {
+ if (message.status_code !== Soup.Status.OK) {
+ callback(false, message.status_code, null);
+ return;
+ }
+
+ callback(true, message.status_code, message.response_body.data);
+ }));
+
+ },
+
+ closeChangeset: function(changesetId, callback) {
+ let url = this._getCloseChangesetUrl(changesetId);
+ let uri = new Soup.URI(url);
+ let msg = new Soup.Message({ method: 'PUT',
+ uri: uri });
+
+ this._session.queue_message(msg, (function(obj, message) {
+ if (message.status_code !== Soup.Status.OK) {
+ callback(false, message.status_code);
+ return;
+ }
+
+ callback(true, message.status_code);
+ }));
+ },
+
+ _getOpenChangesetUrl: function() {
+ return this._getBaseUrl() + '/' + API_VERSION + '/changeset/create';
+ },
+
+ _getCloseChangesetUrl: function(changesetId) {
+ return this._getBaseUrl() + '/' + API_VERSION + '/changeset/' +
+ changesetId + '/close';
+ },
+
+ _getCreateOrUpdateUrl: function(object, type) {
+ if (object.id)
+ return this._getBaseUrl() + '/' + API_VERSION + '/' + type +
+ '/' + object.id;
+ else
+ return this._getBaseUrl() + '/' + API_VERSION + '/' + type +
+ '/create';
+ },
+
+ _authenticate: function(session, msg, auth, retrying, user_data) {
+ if (retrying)
+ session.cancel_message(msg, Soup.Status.UNAUTHORIZED);
+
+ auth.authenticate(this._username, this._password);
+ }
+});
+
+/*
+ * Gets a status message (usually for an error case)
+ * to show for a given OSM server response.
+ */
+function getStatusMessage(statusCode) {
+ switch (statusCode) {
+ case Soup.Status.IO_ERROR:
+ case Soup.Status.UNAUTHORIZED:
+ // setting the status in session.cancel_message still seems
+ // to always give status IO_ERROR
+ return _("Incorrect user name or password");
+ case Soup.Status.OK:
+ return _("Success");
+ case Soup.Status.BAD_REQUEST:
+ return _("Bad request");
+ case Soup.Status.NOT_FOUND:
+ return _("Object not found");
+ case Soup.Status.CONFLICT:
+ return _("Conflict, someone else has just modified the object");
+ case Soup.Status.GONE:
+ return _("Object has been deleted");
+ case Soup.Status.PRECONDITION_FAILED:
+ return _("Way or relation refers to non-existing children");
+ default:
+ return null;
+ }
+}
+
diff --git a/src/osmEdit.js b/src/osmEdit.js
new file mode 100644
index 0000000..b24f722
--- /dev/null
+++ b/src/osmEdit.js
@@ -0,0 +1,112 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const GObject = imports.gi.GObject;
+const Lang = imports.lang;
+
+const OSMEditDialog = imports.osmEditDialog;
+const OSMConnection = imports.osmConnection;
+const Utils = imports.utils;
+
+const OSMEditManager = new Lang.Class({
+ Name: 'OSMEditManager',
+ Extends: GObject.Object,
+
+ _init: function() {
+ this._osmConnection = new OSMConnection.OSMConnection();
+ },
+
+ get useTestApi() {
+ return this._osmConnection.useTestApi;
+ },
+
+ showEditDialog: function(parentWindow, place) {
+ let dialog = new OSMEditDialog.OSMEditDialog( { transient_for: parentWindow,
+ place: place });
+ let response = dialog.run();
+
+ dialog.destroy();
+ },
+
+ fetchObject: function(place, callback, cancellable) {
+ let osmType = this._getOSMTypeName(place);
+
+ this._osmConnection.getOSMObject(osmType, place.osm_id,
+ (function(success, status, data,
+ type) {
+ callback(success, status, data,
+ type);
+ }), cancellable);
+
+ },
+
+ _getOSMTypeName: function(place) {
+ let osmType;
+
+ switch (place.osm_type) {
+ case 1:
+ osmType = 'node';
+ break;
+ case 2:
+ osmType = 'relation';
+ break;
+ case 3:
+ osmType = 'way';
+ break;
+ default:
+ Utils.debug ('Unknown OSM type: ' + this._place.osm_type);
+ break;
+ }
+
+ return osmType;
+ },
+
+ uploadObject: function(object, type, comment, source, callback) {
+ this._osmConnection.openChangeset(comment, source,
+ function(success, status,
+ changesetId) {
+ if (success)
+ this._uploadObject(object,
+ type,
+ changesetId,
+ callback);
+ else
+ callback(false, status);
+ }.bind(this));
+ },
+
+ _uploadObject: function(object, type, changesetId, callback) {
+ this._osmConnection.uploadObject(object, type, changesetId,
+ function(success, status,
+ response) {
+ if (success)
+ this._closeChangeset(changesetId,
+ callback);
+ else
+ callback(false, status);
+ }.bind(this));
+ },
+
+ _closeChangeset: function(changesetId, callback) {
+ this._osmConnection.closeChangeset(changesetId, callback);
+ }
+});
diff --git a/src/osmEditDialog.js b/src/osmEditDialog.js
new file mode 100644
index 0000000..8aa20a2
--- /dev/null
+++ b/src/osmEditDialog.js
@@ -0,0 +1,122 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+
+const Application = imports.application;
+
+const Response = {
+ SUCCESS: 0,
+ CANCELLED: 1,
+};
+
+const OSMEditDialog = new Lang.Class({
+ Name: 'OSMEditDialog',
+ Extends: Gtk.Dialog,
+ Template: 'resource:///org/gnome/Maps/ui/osm-edit-dialog.ui',
+ InternalChildren: [ 'cancelButton',
+ 'saveButton',
+ 'stack',
+ 'nameEntry',
+ 'commentEntry',
+ 'sourceEntry'],
+
+ _init: function(params) {
+ this._place = params.place;
+ delete params.place;
+
+ // This is a construct-only property and cannot be set by GtkBuilder
+ params.use_header_bar = true;
+
+ this.parent(params);
+
+ this._cancellable = new Gio.Cancellable();
+ this._cancellable.connect((function() {
+ this.response(Response.CANCELLED);
+ }).bind(this));
+
+ this.connect('delete-event', (function() {
+ this._cancellable.cancel();
+ }).bind(this));
+
+ this._isEditing = false;
+ this._saveButton.connect('clicked', this._onSaveClicked.bind(this));
+
+ Application.osmEditManager.fetchObject(this._place,
+ this._fetchOSMObjectCB.bind(this),
+ this._cancellable);
+ },
+
+ _onSaveClicked: function() {
+ if (this._isEditing) {
+ // switch to the upload view
+ this._stack.set_visible_child_name('upload');
+ this._saveButton.label = _("Upload");
+ this._isEditing = false;
+ } else {
+ // upload data to OSM
+ Application.osmEditManager.uploadObject(this._osmObject,
+ this._commentEntry.text,
+ this._sourceEntry.text,
+ this._uploadOSMObjectCB);
+ }
+ },
+
+ _fetchOSMObjectCB: function(success, status, data) {
+ if (success) {
+ this._loadOSMData(data);
+ } else {
+ this._showError(status);
+ }
+ },
+
+ _loadOSMData: function(data) {
+ this._osmObject = data;
+ this._isEditing = true;
+ this._initEditWidgets();
+ this._stack.set_visible_child_name('editor');
+ },
+
+ _uploadOSMObjectCB: function(success, status) {
+ // TODO: show error dialog (might need more info from the connection layer)
+ // TODO: close the dialog on sucess
+ },
+
+ _showError: function(status) {
+
+ },
+
+ _initEditWidgets: function() {
+ // TODO: for now, just use a static name text entry
+ let name = this._osmObject.getTag('name');
+
+ if (name)
+ this._nameEntry.text = this._osmObject.getTag('name');
+
+ this._nameEntry.connect('changed', (function() {
+ this._osmObject.setTag('name', this._nameEntry.text);
+ this._saveButton.sensitive = true;
+ }).bind(this));
+ }
+});
diff --git a/src/osmUtils.js b/src/osmUtils.js
new file mode 100644
index 0000000..c9d36e4
--- /dev/null
+++ b/src/osmUtils.js
@@ -0,0 +1,57 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2015 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const Soup = imports.gi.Soup;
+
+const Application = imports.application;
+
+/*
+ * Gets a Wikipedia article in OSM tag format (i.e. lang:Article title)
+ * given a URL or null if input doesn't match a Wikipedia URL
+ */
+function getWikipediaOSMArticleFormatFromUrl(url) {
+ let regex = /https?:\/\/(..)\.wikipedia\.org\/wiki\/(.+)/;
+ let match = url.match(regex);
+
+ if (match && match.length == 3) {
+ let lang = match[1];
+ let article = match[2];
+
+ return lang + ':' + Soup.uri_decode(article).replace(/_/g, ' ');
+ } else {
+ return null;
+ }
+}
+
+/**
+ * Updates a Place object according to an OSMObject.
+ * Will also update place in the place store.
+ */
+function updatePlaceFromOSMObject(place, osmObject) {
+ place.name = osmObject.get_tag('name');
+ place.population = osmObject.get_tag('population');
+ place.wiki = osmObject.get_tag('wikipedia');
+ place.openingHours = osmObject.get_tag('opening_hours');
+ place.wheelchair = osmObject.get_tag('wheelchair');
+
+ Application.placeStore.updatePlace(place);
+}
diff --git a/src/placeBubble.js b/src/placeBubble.js
index 98bcce4..8ba6549 100644
--- a/src/placeBubble.js
+++ b/src/placeBubble.js
@@ -19,6 +19,7 @@
* Author: Damián Nohales <damiannohales gmail com>
*/
+const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Format = imports.format;
const Lang = imports.lang;
@@ -26,12 +27,26 @@ const Lang = imports.lang;
const Application = imports.application;
const ContactPlace = imports.contactPlace;
const MapBubble = imports.mapBubble;
+const OSMConnection = imports.osmConnection;
+const OSMUtils = imports.osmUtils;
const Overpass = imports.overpass;
const Place = imports.place;
const PlaceFormatter = imports.placeFormatter;
const PlaceStore = imports.placeStore;
const Utils = imports.utils;
+// enumeration representing
+// the different OSM editing
+// field types
+const EditFieldType = {
+ // plain text
+ TEXT: 0,
+ // integer value (e.g. population)
+ INTEGER: 1,
+ // selection of yes|no|limited|designated
+ YES_NO_LIMITED_DESIGNATED: 2
+}
+
const PlaceBubble = new Lang.Class({
Name: 'PlaceBubble',
Extends: MapBubble.MapBubble,
@@ -41,7 +56,8 @@ const PlaceBubble = new Lang.Class({
'box-content',
'label-title']);
params.buttons = (MapBubble.Button.ROUTE |
- MapBubble.Button.SEND_TO);
+ MapBubble.Button.SEND_TO |
+ MapBubble.Button.EDIT);
// We do not serialize contacts to file, so adding them
// as favourites does not makes sense right now.
@@ -79,6 +95,14 @@ const PlaceBubble = new Lang.Class({
}).bind(this));
}
this.content.add(this._stack);
+
+ this._initEditButton(this._editButton);
+ this._initOSMEditor();
+
+ this._cancellable = new Gio.Cancellable();
+ this.connect('delete-event', (function() {
+ this._cancellable.cancel();
+ }).bind(this));
},
_formatWikiLink: function(wiki) {
@@ -134,5 +158,311 @@ const PlaceBubble = new Lang.Class({
}).bind(this));
this._stack.visible_child = this._boxContent;
+ },
+
+ // clear the view widgets to be able to re-polute an updated place
+ _clearView: function() {
+ let widgets = this._boxContent.get_children();
+
+ // remove the dynamically added content, the title label
+ // has position 0 in the box
+ for (let i = 1; i < widgets.length; i++) {
+ this._boxContent.remove(widgets[i]);
+ }
+ },
+
+ _initEditButton: function(button) {
+ button.connect('clicked', this._onEditClicked.bind(this));
+ },
+
+ _onEditClicked: function() {
+ this._viewOrEditStack.visible_child_name = 'loading';
+ Application.osmEditManager.fetchObject(this._place,
+ this._onObjectFetched.bind(this),
+ this._cancellable);
+ },
+
+ _initOSMEditor: function() {
+ this._saveButton.connect('clicked', this._onSaveClicked.bind(this));
+ this._cancelButton.connect('clicked', this._onCancelClicked.bind(this));
+ this._setupEditingButtons();
+ },
+
+ _onObjectFetched: function(success, status, osmObject, osmType) {
+ if (success) {
+ this._loadOSMData(osmObject, osmType);
+ // keep the save button insensitive until the user has done a change
+ this._saveButton.sensitive = false;
+ } else {
+ this._showError(status);
+ this._viewOrEditStack.visible_child_name = 'view';
+ }
+ },
+
+ // GtkContainer.child_get_property doesn't seem to be usable from GJS
+ _getRowOfDeleteButton: function(button) {
+ for (let row = 0;; row++) {
+ let foundButton = this._editContentArea.get_child_at(2, row);
+
+ if (foundButton === button)
+ return row;
+
+ if (foundButton == null)
+ return -1;
+ }
+ },
+
+ _addOSMEditDeleteButton: function(tag) {
+ let deleteButton = Gtk.Button.new_from_icon_name('user-trash-symbolic',
+ Gtk.IconSize.BUTTON);
+ this._editContentArea.attach(deleteButton, 2, this._nextRow, 1, 1);
+
+ deleteButton.connect('clicked', (function() {
+ this._osmObject.delete_tag(tag);
+
+ let row = this._getRowOfDeleteButton(deleteButton);
+ this._editContentArea.remove_row(row);
+ this._saveButton.sensitive = true;
+ this._nextRow--;
+ this._updateAddDetailMenu();
+ }).bind(this, tag));
+
+ deleteButton.show();
+ },
+
+ _addOSMEditLabel: function(text) {
+ let label = new Gtk.Label({label: text});
+ label.halign = Gtk.Align.END;
+ this._editContentArea.attach(label, 0, this._nextRow, 1, 1);
+
+ // add the label to the size group, to align all labels with the
+ // hard-coded comment label
+ this._editLabelSizeGroup.add_widget(label);
+
+ label.show();
+ },
+
+ _osmWikipediaRewriteFunc: function(text) {
+ let wikipediaArticleFormatted =
+ OSMUtils.getWikipediaOSMArticleFormatFromUrl(text);
+
+ // if the entered text is a Wikipedia link,
+ // substitute it with the OSM-formatted Wikipedia article tag
+ if (wikipediaArticleFormatted)
+ return wikipediaArticleFormatted;
+ else
+ return text;
+ },
+
+ _addOSMEditTextEntry: function(text, tag, value, rewriteFunc) {
+ this._addOSMEditLabel(text);
+
+ let entry = new Gtk.Entry();
+ entry.text = value;
+
+ entry.connect('changed', (function() {
+ if (rewriteFunc)
+ entry.text = rewriteFunc(entry.text);
+ this._osmObject.set_tag(tag, entry.text);
+ this._saveButton.sensitive = true;
+ }).bind(this, tag, entry));
+
+ this._editContentArea.attach(entry, 1, this._nextRow, 1, 1);
+ entry.show();
+
+ // TODO: should we allow deleting the name field?
+ this._addOSMEditDeleteButton(tag);
+
+ this._nextRow++;
+ },
+
+ _addOSMEditIntegerEntry: function(text, tag, value) {
+ this._addOSMEditLabel(text);
+
+ let spinbutton = Gtk.SpinButton.new_with_range(0, 1e9, 1);
+ spinbutton.value = value;
+ spinbutton.numeric = true;
+ spinbutton.connect('changed', (function() {
+ this._osmObject.set_tag(tag, spinbutton.text);
+ this._saveButton.sensitive = true;
+ }).bind(this, tag, spinbutton));
+
+ this._editContentArea.attach(spinbutton, 1, this._nextRow, 1, 1);
+ spinbutton.show();
+
+ this._addOSMEditDeleteButton(tag);
+
+ this._nextRow++;
+ },
+
+ _addOSMEditYesNoLimitedDesignated: function(text, tag, value) {
+ this._addOSMEditLabel(text);
+
+ let combobox = new Gtk.ComboBoxText();
+
+ combobox.append('yes', _("Yes"));
+ combobox.append('no', _("No"));
+ combobox.append('limited', _("Limited"));
+ combobox.append('designated', _("Designated"));
+
+ combobox.active_id = value;
+ combobox.connect('changed', (function() {
+ this._osmObject.set_tag(tag, combobox.active_id);
+ this._saveButton.sensitive = true;
+ }).bind(this, tag, combobox));
+
+ this._editContentArea.attach(combobox, 1, this._nextRow, 1, 1);
+ combobox.show();
+
+ this._addOSMEditDeleteButton(tag);
+
+ this._nextRow++;
+ },
+
+ // update visible items in the ad
+ _updateAddDetailMenu: function() {
+ let name = this._osmObject.get_tag('name');
+ let wikipedia = this._osmObject.get_tag('wikipedia');
+ let population = this._osmObject.get_tag('population');
+ let wheelchair = this._osmObject.get_tag('wheelchair');
+
+ this._addNameButton.visible = (name == null);
+ this._addWikipediaButton.visible = (wikipedia == null);
+ this._addPopulationButton.visible = (population == null);
+ this._addWheelchairButton.visible = (wheelchair == null);
+
+ // update sensitiveness of the add details button, set it as
+ // insensitive if all tags when support editing is already present
+ this._addDetailButton.sensitive =
+ (name == null || wikipedia == null || population == null ||
+ wheelchair == null);
+ },
+
+ // helper function to connect an add menu item
+ // to a corresponding callback to add the appropriate
+ // OSM tag
+ _connectAddEditFieldButton: function(button, label, tag, type,
+ rewriteFunc) {
+ button.connect('clicked', (function() {
+ this._addDetailButton.active = false;
+
+ // add a "placeholder" empty OSM tag to keep the add detail menu
+ // updated, these tags will be filtered out if nothing is entered
+ this._osmObject.set_tag(tag, '');
+ this._updateAddDetailMenu();
+
+ switch (type) {
+ case EditFieldType.TEXT:
+ this._addOSMEditTextEntry(label, tag, '', rewriteFunc);
+ break;
+ case EditFieldType.INTEGER:
+ this._addOSMEditIntegerEntry(label, tag, '');
+ break;
+ case EditFieldType.YES_NO_LIMITED_DESIGNATED:
+ this._addOSMEditYesNoLimitedDesignated(label, tag, '');
+ break;
+ }
+ }).bind(this, label, tag, type));
+ },
+
+ // set up the menu entries for adding OSM tags
+ _setupEditingButtons: function() {
+ this._connectAddEditFieldButton(this._addNameButton,
+ _("Name"), 'name',
+ EditFieldType.TEXT);
+ this._connectAddEditFieldButton(this._addWikipediaButton,
+ _("Wikipedia"), 'wikipedia',
+ EditFieldType.TEXT,
+ this._osmWikipediaRewriteFunc);
+ this._connectAddEditFieldButton(this._addPopulationButton,
+ _("Population"), 'population',
+ EditFieldType.INTEGER);
+ this._connectAddEditFieldButton(this._addWheelchairButton,
+ _("Wheelchair"), 'wheelchair',
+ EditFieldType.YES_NO_LIMITED_DESIGNATED);
+
+ // initially set unexisting changeset comment
+ this._changesetComment = null;
+ this._editCommentEntry.connect('changed', (function() {
+ this._changesetComment = this._editCommentEntry.text;
+ }).bind(this));
+ },
+
+ _loadOSMData: function(osmObject, osmType) {
+ this._osmObject = osmObject;
+ this._osmType = osmType;
+
+ // clear any previos editing widgets
+ let widgets = this._editContentArea.get_children();
+ for (let i = 0; i < widgets.length; i++) {
+ this._editContentArea.remove(widgets[i]);
+ }
+
+ // create edit widgets
+ this._nextRow = 0;
+
+ let name = osmObject.get_tag('name');
+ if (name != null)
+ this._addOSMEditTextEntry(_("Name"), 'name', name);
+
+ let wikipedia = osmObject.get_tag('wikipedia');
+ if (wikipedia != null)
+ this._addOSMEditTextEntry(_("Wikipedia"), 'wikipedia', wikipedia,
+ this._osmWikipediaRewriteFunc);
+
+ let population = osmObject.get_tag('population');
+ if (population != null)
+ this._addOSMEditIntegerEntry(_("Population"), 'population',
+ population);
+
+ let wheelchair = osmObject.get_tag('wheelchair');
+ if (wheelchair != null)
+ this._addOSMEditYesNoLimitedDesignated(_("Wheelchair access"),
+ 'wheelchair', wheelchair);
+
+ this._updateAddDetailMenu();
+ this._viewOrEditStack.visible_child_name = 'edit';
+ },
+
+ _showError: function(status) {
+ let statusMessage = OSMConnection.getStatusMessage(status);
+ let messageDialog =
+ new Gtk.MessageDialog({ transient_for: this.get_toplevel(),
+ destroy_with_parent: true,
+ message_type: Gtk.MessageType.ERROR,
+ buttons: Gtk.ButtonsType.OK,
+ modal: true,
+ text: _("An error has occurred"),
+ secondary_text: statusMessage });
+
+ messageDialog.run();
+ messageDialog.destroy();
+ },
+
+ _onSaveClicked: function() {
+ Application.osmEditManager.uploadObject(this._osmObject,
+ this._osmType,
+ this._changesetComment,
+ null,
+ this._uploadObjectCB.bind(this));
+ },
+
+ _onCancelClicked: function() {
+ this._viewOrEditStack.visible_child_name = 'view';
+ },
+
+ _uploadObjectCB: function(success, status) {
+ if (success) {
+ if (!Application.osmEditManager.useTestApi) {
+ // update place
+ OSMUtils.updatePlaceFromOSMObject(this._place, this._osmObject);
+ // refresh place view
+ this._clearView();
+ this._populate(this._place);
+ }
+ } else
+ this._showError(status);
+
+ this._viewOrEditStack.visible_child_name = 'view';
}
});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]