Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement #2 address details option #58

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions openpoiservice/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from flask_cors import CORS
from openpoiservice.server.categories.categories import CategoryTools
from openpoiservice.server.api import api_exceptions
from openpoiservice.server.db_import.objects import GeocoderSetup
import yaml
import os
import time
Expand All @@ -19,6 +20,13 @@
basedir = os.path.abspath(os.path.dirname(__file__))
ops_settings = yaml.safe_load(open(os.path.join(basedir, 'ops_settings.yml')))

geocoder = None
geocode_categories = None
if ops_settings['geocoder'] is not None:
Copy link
Member

@MichaelsJP MichaelsJP Mar 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally it's better to define those recurring strings in a "static" object somewhere. In this case you could define a static settings variable class in openpoiservice.server and import it alongside the ops_settings object etc.
Another option is that you define all the config file variables you need once in the openpoiservice.server and import these instead of ops_settings.
The reason is, it will reduce complexity and coding time when you have those strings in one place. Once you change something in the config file you'll only have to adjust one variable in the openpoiservice.server file. My advice would be to follow option 2.
E.g.

  1. Solution, define a variable class holding the static variable strings:
>>> class MyConfigVariableClass:
...     geocoderSettingsVariable = 'geocoder'
...
>>> MyConfigVariableClass.geocoderSettingsVariable
'geocoder'
  1. Solution, define global variables in openpoiservice.server, assign config parameters to it and import them wherever you need them
someSettings = ops_settings['some_config_parameter']['some_sub_config_parameter']

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those strings won't be changed or modified by the user. He will just add further parameters, which will be checked the way you showed. Adjust the strings anyway?

geocoder = GeocoderSetup(list(ops_settings['geocoder'].items())[0]).define_geocoder()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. See above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

geocode_categories = CategoryTools('categories.yml').generate_geocode_categories()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. See above



if "TESTING" in os.environ:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you have to decide if you replace the strings. I think this is a one time call so just leave it like that.

ops_settings['provider_parameters']['table_name'] = ops_settings['provider_parameters']['table_name'] + '_test'

Expand Down
2 changes: 0 additions & 2 deletions openpoiservice/server/api/pois_post.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,6 @@ definitions:
properties:
name:
type: "string"
address:
type: "string"
website:
type: "string"
opening_hours:
Expand Down
32 changes: 18 additions & 14 deletions openpoiservice/server/api/query_builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# openpoiservice/server/query_builder.py

from openpoiservice.server import db
from openpoiservice.server import categories_tools, ops_settings
from openpoiservice.server import categories_tools, ops_settings, geocoder
import geoalchemy2.functions as geo_func
from geoalchemy2.types import Geography, Geometry
from geoalchemy2.elements import WKBElement, WKTElement
Expand All @@ -13,6 +13,7 @@
from sqlalchemy import dialects
import geojson as geojson
import logging
import json
from timeit import default_timer as timer

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -105,7 +106,8 @@ def request_pois(self):
bbox_query.c.geom,
keys_agg,
values_agg,
categories_agg) \
categories_agg,
bbox_query.c.address) \
.order_by(*sortby_group) \
.filter(*category_filters) \
.filter(*custom_filters) \
Expand All @@ -115,14 +117,7 @@ def request_pois(self):
.group_by(bbox_query.c.osm_id) \
.group_by(bbox_query.c.osm_type) \
.group_by(bbox_query.c.geom) \
# .all()

# end = timer()
# print(end - start)

# print(str(pois_query))
# for dude in pois_query:
# print(dude)
.group_by(bbox_query.c.address)

# response as geojson feature collection
features = self.generate_geojson_features(pois_query, params['limit'])
Expand All @@ -147,12 +142,9 @@ def generate_geom_filters(geometry, Pois):
type_coerce(geom_bbox, Geography)), Pois.geom, 0))

elif 'bbox' not in geometry and 'geom' in geometry:

geom = geometry['geom'].wkt

filters.append( # buffer around geom
geo_func.ST_DWithin(geo_func.ST_Buffer(type_coerce(geom, Geography), geometry['buffer']), Pois.geom, 0)
)
geo_func.ST_DWithin(geo_func.ST_Buffer(type_coerce(geom, Geography), geometry['buffer']), Pois.geom, 0))

return filters, geom

Expand Down Expand Up @@ -264,12 +256,24 @@ def generate_geojson_features(cls, query, limit):
}
properties["category_ids"] = category_ids_obj

# Checks if Tags are available
if q[5][0] is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try-except like in the array call in the following lines?

key_values = {}
for idx, key in enumerate(q[4]):
key_values[key] = q[5][idx]
properties["osm_tags"] = key_values

# Checks if addresses are available
try:
if q[7] is not None:
address_dict = {}
address_data = json.loads(q[7])
for key, value in address_data.items():
address_dict[key] = value
properties['address'] = address_dict
except IndexError:
pass

geojson_feature = geojson.Feature(geometry=trimmed_point,
properties=properties)
geojson_features.append(geojson_feature)
Expand Down
13 changes: 13 additions & 0 deletions openpoiservice/server/categories/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,16 @@ def get_category(self, tags):
categories.append(category_id)

return categories

def generate_geocode_categories(self):

geocode_categories = {}

for category in self.categories_object.items():
if 'geocoder' not in category[1]:
for child in category[1]['children'].values():
for id in child.values():
geocode_categories[id] = {}

return geocode_categories

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general it's better to have no nested for-loops. Think about moving every for-loop to a different function...

1 change: 1 addition & 0 deletions openpoiservice/server/categories/categories.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ education:

facilities:
id: 160
geocoder: False
children:
amenity:
compressed_air: 161
Expand Down
5 changes: 3 additions & 2 deletions openpoiservice/server/db_import/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# openpoiservice/server/models.py

from openpoiservice.server import db, ops_settings
from openpoiservice.server import db, ops_settings, geocoder
from geoalchemy2 import Geography
import logging

Expand All @@ -14,7 +14,6 @@ class Pois(db.Model):
uuid = db.Column(db.LargeBinary, primary_key=True)
osm_id = db.Column(db.BigInteger, nullable=False, index=True)
osm_type = db.Column(db.Integer, nullable=False)
# address = db.Column(db.Text, nullable=True)
geom = db.Column(Geography(geometry_type="POINT", srid=4326, spatial_index=True), nullable=False)

tags = db.relationship("Tags", backref='{}'.format(ops_settings['provider_parameters']['table_name']),
Expand All @@ -23,6 +22,8 @@ class Pois(db.Model):
categories = db.relationship("Categories", backref='{}'.format(ops_settings['provider_parameters']['table_name']),
lazy='dynamic')

address = db.Column(db.String, nullable=True)

def __repr__(self):
return '<osm id %r>' % self.osm_id

Expand Down
65 changes: 61 additions & 4 deletions openpoiservice/server/db_import/objects.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# openpoiservice/server/poi_entity.py

import json
import logging
from geopy.geocoders import get_geocoder_for_service
# from geopy.extra.rate_limiter import RateLimiter

logger = logging.getLogger(__name__)

class PoiObject(object):

def __init__(self, uuid, categories, osmid, lat_lng, osm_type):
def __init__(self, uuid, categories, osmid, lat_lng, osm_type, address=None):
self.uuid = uuid
self.osmid = int(osmid)
self.type = int(osm_type)
Expand All @@ -14,9 +20,7 @@ def __init__(self, uuid, categories, osmid, lat_lng, osm_type):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove dead code?

self.geom = 'SRID={};POINT({} {})'.format(4326, float(lat_lng[0]),
float(lat_lng[1]))

# add geocoder connector here...
self.address = None
self.address = address


class TagsObject(object):
Expand All @@ -26,3 +30,56 @@ def __init__(self, uuid, osmid, key, value):
self.osmid = int(osmid)
self.key = key
self.value = value


class GeocoderSetup(object):
"""Initialises geocoder"""

def __init__(self, geocoder_name):
self.geocoder_name = geocoder_name
self.geocoder = None

def define_geocoder(self):

# returns warning if no valid geocoder is provided
try:
self.geocoder_settings = get_geocoder_for_service(self.geocoder_name[0])

except Exception as err_geocoder:
logger.warning(err_geocoder)
return err_geocoder

# returns warning if no valid geocoder settings are provided
try:
if self.geocoder_name[1] is not None:
self.geocoder = self.geocoder_settings(**self.geocoder_name[1])

else:
self.geocoder = self.geocoder_settings()
return self.geocoder

except Exception as err_parameter:
logger.warning(err_parameter)
return err_parameter


class AddressObject(object):

def __init__(self, lat_lng, geocoder):
self.lat_lng = lat_lng[::-1]
self.geocoder = geocoder

def address_request(self):

# address_delaied = RateLimiter(self.geocoder.reverse, min_delay_seconds=1)
response = self.geocoder.reverse(query=self.lat_lng)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a try-except here to catch possible connection errors, e.g. resource not available etc.


# Checks if address for location is available
if response is not None:
return json.dumps(response.raw)
# try:
# return json.dumps(response.raw)
# except AttributeError:
# return json.dumps(response)

return None
33 changes: 24 additions & 9 deletions openpoiservice/server/db_import/parse_osm.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# openpoiservice/server/parse_osm.py

from openpoiservice.server import db
from openpoiservice.server import categories_tools, ops_settings
from openpoiservice.server import categories_tools, ops_settings, geocoder, geocode_categories
from openpoiservice.server.db_import.models import Pois, Tags, Categories
from openpoiservice.server.db_import.objects import PoiObject, TagsObject
from openpoiservice.server.db_import.objects import PoiObject, TagsObject, AddressObject
from openpoiservice.server.utils.decorators import get_size
import shapely as shapely
from shapely.geometry import Point, Polygon, LineString, MultiPoint
Expand Down Expand Up @@ -179,12 +179,21 @@ def store_poi(self, poi_object):
"""

self.pois_cnt += 1
self.poi_objects.append(Pois(
uuid=poi_object.uuid,
osm_id=poi_object.osmid,
osm_type=poi_object.type,
geom=poi_object.geom
))
if geocoder is not None:
self.poi_objects.append(Pois(
uuid=poi_object.uuid,
osm_id=poi_object.osmid,
osm_type=poi_object.type,
geom=poi_object.geom,
address=poi_object.address
))
else:
self.poi_objects.append(Pois(
uuid=poi_object.uuid,
osm_id=poi_object.osmid,
osm_type=poi_object.type,
geom=poi_object.geom
))

if self.pois_cnt % 1000 == 0:
logger.info('Pois: {}, tags: {}, categories: {}'.format(self.pois_cnt, self.tags_cnt, self.categories_cnt))
Expand Down Expand Up @@ -261,7 +270,13 @@ def create_poi(self, tags, osmid, lat_lng, osm_type, categories=[]):
self.tags_object = TagsObject(my_uuid, osmid, tag, value)
self.store_tags(self.tags_object)

self.poi_object = PoiObject(my_uuid, categories, osmid, lat_lng, osm_type)
address = None
if geocoder is not None and categories[0] in geocode_categories:
address = AddressObject(lat_lng, geocoder).address_request()
time.sleep(1)

self.poi_object = PoiObject(my_uuid, categories, osmid, lat_lng, osm_type, address)

self.store_poi(self.poi_object)

for category in categories:
Expand Down
7 changes: 5 additions & 2 deletions openpoiservice/server/db_import/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

from openpoiservice.server.db_import.parse_osm import OsmImporter
from openpoiservice.server.utils.decorators import timeit, processify
from openpoiservice.server import ops_settings
from openpoiservice.server import ops_settings, geocoder
from imposm.parser import OSMParser
import logging
import time

# from guppy import hpy
from collections import deque
Expand Down Expand Up @@ -50,6 +49,10 @@ def parse_import(osm_file):
coords = OSMParser(concurrency=1, coords_callback=osm_importer.parse_coords_for_ways)
coords.parse(osm_file)

# Checks if geocoder is provided
if geocoder is not None:
logger.info('Importing addresses...')

logger.info('Storing remaining pois')
osm_importer.save_remainder()

Expand Down
30 changes: 25 additions & 5 deletions openpoiservice/server/ops_settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ concurrent_workers: 4
# Database parameters
provider_parameters:
table_name: ops_planet_pois
db_name: gis
user_name: gis_admin
db_name: opsdb
user_name: postgres
table_schema: public
#user_name: admin
password: admin
password: ops
host: localhost
port: 5434
port: 5432
port_tests: 5432
# add any additional tags you want to save to the database
column_mappings:
Expand All @@ -40,3 +39,24 @@ column_mappings:
phone:
# https://wiki.openstreetmap.org/wiki/Key:website
website:
# choose one geopy geocoder and provide all needed parameters - https://geopy.readthedocs.io/
geocoder:
# ArcGIS:
# domain: 'geocode.arcgis.com'
# pelias:
# domain: 'api.geocode.earth'
# api_key: ''
# timeout: 60
# nominatim:
# timeout: 60
# GeocodeFarm:
# timeout: 60
# Azure:
# subscription_key: ''
# domain: 'atlas.microsoft.com'
# Baidu:
# api_key: ''
# BANFrance:
# domain: 'api-adresse.data.gouv.fr'
# Bing:
# api_key: ''
Loading