Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Osm chaintegration #171

Open
wants to merge 2 commits into
base: develop
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
10 changes: 10 additions & 0 deletions backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ def add_api_endpoints(app):
app.logger.debug("Adding routes to API endpoints")
api = Api(app)

#OSMCHA API
from backend.api.osmcha.resources import(
OSMAPI,
GETOSMAPI,
)

# Projects API import
from backend.api.projects.resources import (
ProjectsRestAPI,
Expand Down Expand Up @@ -268,6 +274,10 @@ def add_api_endpoints(app):
from backend.api.system.applications import SystemApplicationsRestAPI
from backend.api.system.image_upload import SystemImageUploadRestAPI

#OSMCHA endpoint
api.add_resource(OSMAPI, format_url('osmcha/'), methods=['POST'])
api.add_resource(GETOSMAPI, format_url('osmcha/<int:project_id>'), methods=['GET'])

# Projects REST endpoint
api.add_resource(ProjectsAllAPI, format_url("projects/"), methods=["GET"])
api.add_resource(
Expand Down
107 changes: 107 additions & 0 deletions backend/api/osmcha/resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import io
from distutils.util import strtobool

from flask import send_file, Response
from flask_restful import Resource, current_app, request
from schematics.exceptions import DataError

from backend.services.mapping_service import MappingService, NotFound
from backend.models.dtos.grid_dto import GridDTO
from backend.models.dtos.osmcha_dto import (
OsmDTO,
)
from backend.models.dtos.user_dto import UserDTO
from backend.services.users.authentication_service import token_auth, tm
from backend.services.users.user_service import UserService
from backend.services.validator_service import ValidatorService
from backend.services.grid.grid_service import GridService
from backend.models.postgis.statuses import UserRole
from backend.models.postgis.utils import InvalidGeoJson
from backend.services.osmcha_service import OSMCHA



class OSMAPI(Resource):
@token_auth.login_required
def post(self):
"""
Get osm metadata
---
tags:
- osm
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
type: string
default: Token sessionTokenHere=
required: true
- in: body
name: body
required: true
description: JSON object to update osmcha data
schema:
properties:
project_id:
type: int
description: project id
responses:
200:
description: osm data found
404:
description: osm data not found
500:
description: Internal Server Error
"""
try:
osm_dto=OsmDTO(request.get_json())
osm_dto.validate()
osm = OSMCHA.OSM(osm_dto)
return osm, 200

except Exception as e:
error_msg = f"OSMAPI - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch osm data"}, 500


class GETOSMAPI(Resource):
@token_auth.login_required
def get(self, project_id):
"""
Get osm metadata
---
tags:
- osm
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
type: string
default: Token sessionTokenHere=
required: true
- in: path
name: project_id
description: project id
type: integer
required: true
responses:
200:
description: osm data found
404:
description: osm data not found
500:
description: Internal Server Error
"""
try:
osm = OSMCHA.get_osm_data(project_id)
return osm, 200

except Exception as e:
error_msg = f"OSMAPI - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch osm data"}, 500
30 changes: 30 additions & 0 deletions backend/models/dtos/osmcha_dto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from schematics import Model
from schematics.exceptions import ValidationError
from schematics.types import (
StringType,
BaseType,
IntType,
BooleanType,
FloatType,
UTCDateTimeType,
DateType,
)
from schematics.types.compound import ListType, ModelType


class OsmDTO(Model):
"""
OSM details to add in database
"""
project_id = IntType(required=True)

class AddOsmDTO(Model):
'''
OSM details to store data from OSMCha
'''
project_id = IntType(required=True)
task_id = IntType(required=True)
reasons = ListType(StringType,required=True, serialized_name="Reasons")
no_of_flags = IntType(required=True)
changeset_id = ListType(StringType,required=True, serialized_name='changeset_id')
taskhistory_id = IntType(required=True)
181 changes: 181 additions & 0 deletions backend/models/postgis/osmcha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import json
import re
from typing import Optional
from cachetools import TTLCache, cached
import geojson
import datetime
from flask import current_app
from geoalchemy2 import Geometry
from geoalchemy2.shape import to_shape
from sqlalchemy.sql.expression import cast, or_
from sqlalchemy import text, desc, func, Time, orm, literal, distinct
from shapely.geometry import shape
from sqlalchemy.dialects.postgresql import ARRAY
import requests
from backend import db
from backend.models.postgis.user import User
from enum import Enum
from backend.models.postgis.utils import (
timestamp,
NotFound,
)
from backend.models.postgis.task import Task, TaskHistory
from backend.models.dtos.osmcha_dto import OsmDTO, AddOsmDTO


class OSMcha(db.Model):
__tablename__ = 'osmcha'

id = db.Column(db.Integer, primary_key=True)
project_id = db.Column(db.Integer, db.ForeignKey("projects.id"), index=True)
task_id = db.Column(db.Integer)
changeset_id = db.Column(db.String)
taskhistory_id = db.Column(db.BigInteger)
no_of_flags = db.Column(db.Integer)
reasons = db.Column(db.String)

def create(self):
""" Creates and saves the current model to the DB """
db.session.add(self)
db.session.commit()

def update(self):
""" Updates the DB with the current state of the Task """
db.session.commit()

def save(self):
""" Save changes to db"""
db.session.commit()

def delete(self):
""" Deletes the current model from the DB """
db.session.delete(self)
db.session.commit()

def get_osmcha_details(task_id, project_id):
'''
Once the task is mapped corresponding task details is sent to OSMCHA
All the changesets from OSMCha is retrived and updated in database
'''

## Get the geometry details of the task
geometry_query = text('select ST_AsGeoJSON(geometry)::json from tasks where project_id= :proj_id and id= :task_id')
result = db.engine.execute(geometry_query, proj_id=project_id, task_id=task_id).fetchone()
geometry = result[0]
coordinates=geometry['coordinates']
geometry['coordinates']=coordinates[0]

taskhistory_query = (
db.session.query(TaskHistory.id, TaskHistory.action_date, TaskHistory.user_id)
.filter(TaskHistory.task_id==task_id)
.filter(TaskHistory.project_id==project_id)
.order_by(TaskHistory.action_date.desc())
.all()
)
name_query = (db.session.query(User.username).filter(User.id==taskhistory_query[0][2])).all()
name_query = name_query[0]

taskhistory_id = taskhistory_query[0][0]
username = name_query[0]
start_date = taskhistory_query[1][1]
end_date = taskhistory_query[0][1]
changeset_id = []
no_of_flags = 0
reasons = []

task_list_query = (
db.session.query(OSMcha.task_id)
.filter(OSMcha.task_id==task_id)
.filter(OSMcha.project_id==project_id)
.all()
)
tasks = []
if len(task_list_query):
tasks = task_list_query[0]

osmcha_dto = AddOsmDTO()
osmcha_dto.project_id = project_id
osmcha_dto.task_id = task_id
osmcha_dto.changeset_id = changeset_id
osmcha_dto.no_of_flags = no_of_flags
osmcha_dto.reasons = reasons
osmcha_dto.taskhistory_id = taskhistory_id

## API call to OSMCha to fetch the details
headers = {'Authorization': 'Token 244d8f03e1e0a23a788593ccc61dcd8b34e7825c'}
geometry["type"]="Polygon"
geometry=json.dumps(geometry)
day_diff = end_date-start_date
day_diff = day_diff.days
try:
if day_diff>0:
url = 'https://osmcha.org/api/v1/changesets/?page=01&page_size=75&geometry='+geometry+'&users='+username+'&date__gte='+str(start_date)+'&date__lte='+str(end_date)
else:
url = 'https://osmcha.org/api/v1/changesets/?page=01&page_size=75&geometry='+geometry+'&users='+username+'&date__lte='+str(end_date)

osmcha_response = requests.get(url, headers=headers)
response = osmcha_response.json()
if (osmcha_response.status_code==200):
if len(response['features'])==0:
if task_id in tasks:
OSMcha.update_osmcha_details(osmcha_dto)
else:
OSMcha.add_osmcha_details(osmcha_dto)
else:
for i in range(len(response['features'])):
changeset_value = response['features'][i]
changeset_id.append(changeset_value['id'])
get_reasons = changeset_value['properties']
if get_reasons['features']!=[]:
temp = get_reasons['features']
for feature in range(len(temp)):
te = get_reasons['features'][feature]
re=[]
ids = te['reasons']
for id_val in range(len(ids)):
for reason in range(len(get_reasons['reasons'])):
t = get_reasons['reasons'][reason]
if ids[id_val]==t['id']:
re.append(t)
break
te['reasons'] = re
reasons.append(temp[feature])
no_of_flags += len(get_reasons['features'])

reasons =json.dumps(reasons)
changeset_id = json.dumps(changeset_id)
osmcha_dto.changeset_id = changeset_id
osmcha_dto.no_of_flags = no_of_flags
osmcha_dto.reasons = reasons
if task_id in tasks:
OSMcha.update_osmcha_details(osmcha_dto)
else:
OSMcha.add_osmcha_details(osmcha_dto)
else:
if task_id in tasks:
pass
else:
OSMcha.add_osmcha_details(osmcha_dto)
except:
if task_id in tasks:
pass
else:
OSMcha.add_osmcha_details(osmcha_dto)



def add_osmcha_details(osm_dto: AddOsmDTO):
"""
Adds the OSMCha details in the osmcha database
"""
osm_var = OSMcha(project_id=osm_dto.project_id, task_id=osm_dto.task_id, changeset_id=osm_dto.changeset_id, taskhistory_id=osm_dto.taskhistory_id, no_of_flags=osm_dto.no_of_flags, reasons=osm_dto.reasons)
db.session.add(osm_var)
db.session.commit()

def update_osmcha_details(osm_dto: AddOsmDTO):
"""
Updates the OSMCha details for particular task with latest data
"""
osm_query = text('update osmcha set changeset_id= :ch_id, no_of_flags= :flags, reasons= :reasons where project_id= :proj_id and task_id= :task_id')
result = db.engine.execute(osm_query, ch_id=osm_dto.changeset_id, flags=osm_dto.no_of_flags, reasons=osm_dto.reasons, proj_id=osm_dto.project_id, task_id=osm_dto.task_id)

4 changes: 2 additions & 2 deletions backend/services/mapping_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from backend.services.messaging.message_service import MessageService
from backend.services.project_service import ProjectService
from backend.services.stats_service import StatsService

from backend.models.postgis.osmcha import OSMcha

class MappingServiceError(Exception):
""" Custom Exception to notify callers an error occurred when handling mapping """
Expand Down Expand Up @@ -139,7 +139,7 @@ def unlock_task_after_mapping(mapped_task: MappedTaskDTO) -> TaskDTO:
)

task.unlock_task(mapped_task.user_id, new_state, mapped_task.comment)

OSMcha.get_osmcha_details(mapped_task.task_id, mapped_task.project_id)
return task.as_dto_with_instructions(mapped_task.preferred_locale)

@staticmethod
Expand Down
Loading