class Editor(GridLayout): current_layer = None edit_last_move = None markers = [] mode = StringProperty("polygon") is_editing = BooleanProperty(False) map_source = ObjectProperty() geojson_fn = StringProperty() title = StringProperty() def __init__(self, **kwargs): super(Editor, self).__init__(**kwargs) self.marker_layer = MarkerMapLayer() self.current_layer = GeoJsonMapLayer() self.result_layer = GeoJsonMapLayer() self.result_layer.geojson = kwargs["geojson"] self.geojson_fn = kwargs["geojson_fn"] self.ids.mapview.add_widget(self.current_layer) self.ids.mapview.add_widget(self.result_layer) self.ids.mapview.add_widget(self.marker_layer) def on_touch_down(self, touch): if self.ids.mapview.collide_point(*touch.pos): if touch.is_double_tap and not self.is_editing: feature = self.select_feature(*touch.pos) if feature: self.edit_feature(feature) else: touch.grab(self) self.forward_to_object(touch) return True elif touch.is_double_tap and self.is_editing: if self.remove_marker_at(touch): return True touch.grab(self) return self.forward_to_object(touch) elif not self.is_editing: self.select_feature(*touch.pos) return self.ids.mapview.on_touch_down(touch) return super(Editor, self).on_touch_down(touch) def on_touch_move(self, touch): if touch.grab_current == self and self.is_editing: return self.forward_to_object(touch, move=True) return super(Editor, self).on_touch_move(touch) def switch_mode(self): if self.is_editing: self.finalize_object() return self.mode = "line" if self.mode == "polygon" else "polygon" def forward_to_object(self, touch, move=False): mapview = self.ids.mapview if not self.is_editing: self.is_editing = True self.clear_markers() coord = mapview.get_latlon_at(*touch.pos) if move: m = self.markers[-1] else: m = EditorMarker() m.mapview = mapview m.editor = self self.markers.append(m) self.marker_layer.add_widget(m) self.marker_layer.reposition() m.lat = coord.lat m.lon = coord.lon self._update_geojson() return True def clear_markers(self): while self.markers: self.remove_marker(self.markers.pop()) def remove_marker(self, m): m.mapview = m.editor = None self.marker_layer.remove_widget(m) if m in self.markers: self.markers.remove(m) self._update_geojson() def remove_marker_at(self, touch): pos = self.ids.mapview.to_local(*touch.pos) for marker in self.markers[:]: if marker.collide_point(*pos): self.remove_marker(marker) return True def finalize_object(self): if self.markers: geojson = self.current_layer.geojson if "properties" not in geojson: geojson["features"][0]["properties"] = {"title": self.title} else: geojson["features"][0]["properties"]["title"] = self.title self.result_layer.geojson["features"].extend(geojson["features"]) self.ids.mapview.trigger_update(True) self.clear_markers() self.is_editing = False self.title = "" self._update_geojson() def save_geojson(self): with open(self.geojson_fn, "wb") as fd: json.dump(self.result_layer.geojson, fd) def _update_geojson(self): features = [] if self.mode == "polygon": geotype = "Polygon" geocoords = lambda x: [x] else: geotype = "LineString" geocoords = lambda x: x # current commited path if self.markers: coordinates = [[c.lon, c.lat] for c in self.markers] features.append({ "type": "Feature", "properties": {}, "geometry": { "type": geotype, "coordinates": geocoords(coordinates) } }) geojson = {"type": "FeatureCollection", "features": features} self.current_layer.geojson = geojson self.ids.mapview.trigger_update(True) def point_inside_polygon(self, x, y, poly): n = len(poly) inside = False p1x, p1y = poly[0] for i in range(n + 1): p2x, p2y = poly[i % n] if y > min(p1y, p2y) and y <= max(p1y, p2y) and x <= max(p1x, p2x): if p1y != p2y: xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xinters: inside = not inside p1x, p1y = p2x, p2y return inside def select_feature(self, x, y): coord = self.ids.mapview.get_latlon_at(x, y) for feature in self.result_layer.geojson["features"]: if feature["type"] != "Feature": continue geometry = feature["geometry"] if geometry["type"] == "Polygon": if self.point_inside_polygon(coord.lon, coord.lat, geometry["coordinates"][0]): return feature def edit_feature(self, feature): self.result_layer.geojson["features"].remove(feature) #self.current_layer.geojson = feature self.ids.mapview.trigger_update(True) self.title = feature.get("properties", {}).get("title", "") self.is_editing = True self.clear_markers() for c in feature["geometry"]["coordinates"][0]: m = EditorMarker(lon=c[0], lat=c[1]) m.mapview = self.ids.mapview m.editor = self self.marker_layer.add_widget(m) self.markers.append(m) self._update_geojson()