104 lines
3.8 KiB
Python
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)
|