osm_conflate/conflate/data.py
2018-05-28 17:45:01 +03:00

104 lines
3.8 KiB
Python

import math
from . import etree
class SourcePoint:
"""A common class for points. Has an id, latitude and longitude,
and a dict of tags. Remarks are optional for reviewers hints only."""
def __init__(self, pid, lat, lon, tags=None, category=None, remarks=None, region=None):
self.id = str(pid)
self.lat = lat
self.lon = lon
self.tags = {} if tags is None else {
k.lower(): str(v).strip() for k, v in tags.items() if v is not None}
self.category = category
self.dist_offset = 0
self.remarks = remarks
self.region = region
self.exclusive_group = None
def distance(self, other):
"""Calculate distance in meters."""
dx = math.radians(self.lon - other.lon) * math.cos(0.5 * math.radians(self.lat + other.lat))
dy = math.radians(self.lat - other.lat)
return 6378137 * math.sqrt(dx*dx + dy*dy) - self.dist_offset
def __len__(self):
return 2
def __getitem__(self, i):
if i == 0:
return self.lon
elif i == 1:
return self.lat
else:
raise ValueError('A SourcePoint has only lat and lon in a list')
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return hash(self.id)
def __repr__(self):
return 'SourcePoint({}, {}, {}, offset={}, tags={})'.format(
self.id, self.lat, self.lon, self.dist_offset, self.tags)
class OSMPoint(SourcePoint):
"""An OSM points is a SourcePoint with a few extra fields.
Namely, version, members (for ways and relations), and an action.
The id is compound and created from object type and object id."""
def __init__(self, ptype, pid, version, lat, lon, tags=None, categories=None):
super().__init__('{}{}'.format(ptype[0], pid), lat, lon, tags)
self.tags = {k: v for k, v in self.tags.items() if v is not None and len(v) > 0}
self.osm_type = ptype
self.osm_id = pid
self.version = version
self.members = None
self.action = None
self.categories = categories or set()
self.remarks = None
def copy(self):
"""Returns a copy of this object, except for members field."""
c = OSMPoint(self.osm_type, self.osm_id, self.version, self.lat, self.lon, self.tags.copy())
c.action = self.action
c.remarks = self.remarks
c.categories = self.categories.copy()
return c
def is_area(self):
return self.osm_type != 'node'
def is_poi(self):
if self.osm_type == 'node':
return True
if self.osm_type == 'way' and len(self.members) > 2:
return self.members[0] == self.members[-1]
if self.osm_type == 'relation' and len(self.members) > 0:
return self.tags.get('type', None) == 'multipolygon'
return False
def to_xml(self):
"""Produces an XML out of the point data. Disregards the "action" field."""
el = etree.Element(self.osm_type, id=str(self.osm_id), version=str(self.version))
for tag, value in self.tags.items():
etree.SubElement(el, 'tag', k=tag, v=value)
if self.osm_type == 'node':
el.set('lat', str(self.lat))
el.set('lon', str(self.lon))
elif self.osm_type == 'way':
for node_id in self.members:
etree.SubElement(el, 'nd', ref=str(node_id))
elif self.osm_type == 'relation':
for member in self.members:
m = etree.SubElement(el, 'member')
for i, n in enumerate(('type', 'ref', 'role')):
m.set(n, str(member[i]))
return el
def __repr__(self):
return 'OSMPoint({} {} v{}, {}, {}, action={}, tags={})'.format(
self.osm_type, self.osm_id, self.version, self.lat, self.lon, self.action, self.tags)