from osgeo import ogr, osr
import os
from visuallayer import *
from shapely.ops import split, nearest_points
from shapely.geometry import Point, LineString, MultiPoint
import osmnx as ox

class City:

    def __init__(self, CityName):
        self.CityName = CityName
        self.Buildings = {}  # dictionary where the key is the Building ID and the value are the building objects
        self.Crossroads = {}
        self.WaitingAreas = {}
        self.Streets = {}
        self.CensusAreas = {}
        self.srs = None

    def setSRS(self, srs):
        if  self.srs == None:
            self.srs = srs
            print('Added Spatial Reference System')
        else:
            if self.srs != srs:
                #ToDo: update all the data already loaded
                self.srs = srs
                print('Updated Spatial Reference System')

    def loadCrossroadsFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        crossroads_vector = driver.Open(path, 0)
        crossroads_layer = crossroads_vector.GetLayer(0)
        crossroads_srs = crossroads_layer.GetSpatialRef()
        transformCoord = False
        if (self.srs == None):
            self.setSRS(crossroads_srs)
        else:
            if (crossroads_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(crossroads_srs, self.srs)

        for crossroad in crossroads_layer:
            crossroadID = crossroad.GetField('CR325_IDOB')
            crossroad_obj = Crossroad(crossroadID)
            crossroad_obj.description = crossroad.GetField('CR325_DESC')

            # Position
            crossroad_geom = crossroad.GetGeometryRef()

            if (transformCoord):
                crossroad_geom.Transform(transformation)
            crossroad_obj.setPosition((crossroad_geom.GetX(), crossroad_geom.GetY()))

            self.Crossroads[crossroadID] = crossroad_obj

    def loadStreetsFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        streets_vector = driver.Open(path, 0)
        streets_layer = streets_vector.GetLayer(0)
        streets_srs = streets_layer.GetSpatialRef()

        transformCoord = False
        if (self.srs == None):
            self.setSRS(streets_srs)
        else:
            if (streets_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(streets_srs, self.srs)

        for street in streets_layer:
            streetID = street.GetField('CR317_IDOB')
            streetName = street.GetField('CR317_Topo')

            street_geom = street.GetGeometryRef()

            street_obj = Street(streetID, streetName)  # create new street
            if (transformCoord):
                street_geom.Transform(transformation)
            street_obj.wtkGeometry = street_geom.ExportToWkt()  # geoemtry of the stree
            street_obj.length = street.GetField('SHAPE_Leng')  # length of the street
            width_string = street.GetField('CR317_De_1')  # width of the street
            width_types = {'Larghezza maggiore di 7,0 metri': 7, 'Larghezza compresa tra 3,5 metri e 7,0 metri': 5,
                           'Larghezza minore di 3,5 metri': 3}
            if (width_string in width_types):
                street_obj.width = width_types[width_string]
            else:
                street_obj.width = -1

            # load all the descriptions of the road
            street_obj.descriptions.append(street.GetField('CR317_DESC'))
            street_obj.descriptions.append(street.GetField('CR317_De_2'))
            street_obj.descriptions.append(street.GetField('CR317_De_3'))
            street_obj.descriptions.append(street.GetField('CR317_De_4'))
            street_obj.descriptions.append(street.GetField('CR317_De_5'))
            street_obj.descriptions.append(street.GetField('CR317_De_6'))

            # Endpoints
            if (street_geom.GetGeometryName() == 'LINESTRING'):  # n.b. if they are multilinestring it means that we don't have the full street so we can't consider it
                first = street_geom.GetPoint(0)
                last = street_geom.GetPoint(street_geom.GetPointCount() - 1)
                street_obj.endpoints = ((first[0], first[1]), (last[0],last[1]))

            self.Streets[streetID] = street_obj

    def loadBuildingsFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        buildings_pos_vector = driver.Open(path, 0)
        buildings_pos_layer = buildings_pos_vector.GetLayer(0)
        building_pos_srs = buildings_pos_layer.GetSpatialRef()

        transformCoord = False
        if (self.srs == None):
            self.setSRS(building_pos_srs)
        else:
            if (building_pos_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(building_pos_srs, self.srs)

        # Create buildings and set their position and ID of the associated street using the the positin shapefile of the buildings
        for building in buildings_pos_layer:

            buildingID = building.GetField('CR01_IDOBJ')
            building_obj = Building(buildingID)  # new building

            # Position
            building_geom = building.GetGeometryRef()
            if (transformCoord):
                building_geom.Transform(transformation)
            building_obj.setPosition((building_geom.GetX(), building_geom.GetY()))

            # Associated street ID
            building_obj.setStreetID(building.GetField('CR01_STR'))

            # Associated risk
            risk_string = building.GetField('CR01_MSK')
            risk_type = {'A': 4, 'B': 3, 'C1': 2, 'C2': 1}

            if (risk_string in risk_type):
                building_obj.risk = risk_type[risk_string]
            else:
                building_obj.risk = -1

            streetID = building.GetField('CR01_STR')

            # load all the descriptions of the building
            building_obj.descriptions.append(building.GetField('CR01_DESCR'))
            building_obj.descriptions.append(building.GetField('CR01_DES_1'))
            building_obj.descriptions.append(building.GetField('CR01_DES_2'))
            building_obj.descriptions.append(building.GetField('CR01_DES_3'))
            building_obj.descriptions.append(building.GetField('CR01_DES_4'))
            building_obj.descriptions.append(building.GetField('CR01_DES_5'))

            building_obj.buildingType = building.GetField('CR01_DES_5')

            self.Buildings[buildingID] = building_obj  # Add the the building in the list of the buidings in the city
            self.Streets[streetID].listOfBuildings.append(building_obj)
            if (building_obj.risk > self.Streets[streetID].risk):
                self.Streets[streetID].risk = building_obj.risk

    def addBuildingsGeometryFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        buildings_vector = driver.Open(path, 0)
        buildings_layer = buildings_vector.GetLayer(0)
        building_srs = buildings_layer.GetSpatialRef()

        transformCoord = False
        if (self.srs == None):
            self.setSRS(building_srs)
        else:
            if (building_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(building_srs, self.srs)

        # Set the geometry and area of the buildings
        for building in buildings_layer:
            # Set geometry and area of the building
            buildingID = building.GetField('CR01_IDOBJ')
            building_geom = building.GetGeometryRef()

            # self.Buildings[buildingID].area = building_geom.Area()
            if (transformCoord):
                building_geom.Transform(transformation)
            self.Buildings[buildingID].wtkGeometry = building_geom.ExportToWkt()

    def loadCensusAreasFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        c_areas_vector = driver.Open(path, 0)
        c_areas_layer = c_areas_vector.GetLayer(0)
        c_areas_srs = c_areas_layer.GetSpatialRef()

        transformCoord = False
        if (self.srs == None):
            self.setSRS(c_areas_srs)
        else:
            if (c_areas_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(c_areas_srs, self.srs)

        for c_area in c_areas_layer:
            cAreaID = c_area.GetField('SEZ')
            c_area_obj = CensusArea(cAreaID)

            c_area_geom = c_area.GetGeometryRef()

            if (transformCoord):
                c_area_geom.Transform(transformation)

            c_area_obj.wtkGeometry = c_area_geom.ExportToWkt()
            c_area_obj.residents = c_area.GetField('P1')

            #plot_geometry(c_area_geom, fillcolor='green', alpha=0.8)
            for buildingID in self.Buildings:
                #if the geometry of the area intersects with the geometry of the building
                if (c_area_geom.Intersects(ogr.CreateGeometryFromWkt(self.Buildings[buildingID].wtkGeometry))):
                    #plot_geometry(ogr.CreateGeometryFromWkt(self.Buildings[buildingID].wtkGeometry), fillcolor='blue',alpha=0.8)
                    c_area_obj.listOfBuildings.append(buildingID)
                    c_area_obj.nBuildings = c_area_obj.nBuildings +1

            #calcola residenti / n di edifici
            if (c_area_obj.nBuildings !=0):
                npeople = int (c_area_obj.residents /c_area_obj.nBuildings)
                if npeople > 9:
                    npeople = 3
                for buildingID in c_area_obj.listOfBuildings:
                    self.Buildings[buildingID].nPeople=npeople

            self.CensusAreas[cAreaID] = c_area_obj

    def loadWaitingAreasFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        w_areas_pos_vector = driver.Open(path, 0)
        w_areas_pos_layer = w_areas_pos_vector.GetLayer(0)
        w_areas_srs = w_areas_pos_layer.GetSpatialRef()

        transformCoord = False
        if (self.srs == None):
            self.setSRS(w_areas_srs)
        else:
            if (w_areas_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(w_areas_srs, self.srs)


        for w_area in w_areas_pos_layer:
            wAreaID = w_area.GetField('Id')
            w_area_obj = WaitingArea(wAreaID)  # new waiting area

            w_area_geom = w_area.GetGeometryRef()
            if (transformCoord):
                w_area_geom.Transform(transformation)
            w_area_obj.setPosition((w_area_geom.GetX(), w_area_geom.GetY()))
            # wAreaGeom = w_area.GetGeometryRef()

            # w_area.wtkGeometry = wAreaGeom.ExportToWkt()  # geoemetry of the waiting area
            # note that the surface occupying a person from a standstill is 2.5 square meters/person
            w_area_obj.users_capacity = w_area.GetField('utenti')  # number of users
            w_area_obj.residents_capacity = w_area.GetField('abitanti')  # number of users

            self.WaitingAreas[wAreaID] = w_area_obj

    def addWaitingAreasGeometryFromShapefile(self, path):
        driver = ogr.GetDriverByName("ESRI Shapefile")
        w_areas_vector = driver.Open(path, 0)
        w_areas_layer = w_areas_vector.GetLayer(0)
        w_areas_srs = w_areas_layer.GetSpatialRef()

        transformCoord = False
        if (self.srs == None):
            self.setSRS(w_areas_srs)
        else:
            if (w_areas_srs != self.srs):
                transformCoord = True
                transformation = osr.CoordinateTransformation(w_areas_srs, self.srs)

        # Set the geometry and area of the areas
        for wArea in w_areas_layer:
            wAreaID = wArea.GetField('Id')
            w_area_geom = wArea.GetGeometryRef()
            # self.WaitingAreas[wAreaID].area = w_area_geom.Area()
            if (transformCoord):
                w_area_geom.Transform(transformation)
            self.WaitingAreas[wAreaID].wtkGeometry = w_area_geom.ExportToWkt()

    def getBuildingsFromStreetID(self, streetID):
        return City.Streets[streetID].listOfBuildings

    def loadCityFromshapefiles(self, crossroads_path, streets_path, buildings_pos_path, buildings_path, waiting_areas_pos_path, waiting_areas_path, census_areas_path):
        self.loadCrossroadsFromShapefile(crossroads_path)
        self.loadStreetsFromShapefile(streets_path)
        self.loadBuildingsFromShapefile(buildings_pos_path)
        self.addBuildingsGeometryFromShapefile(buildings_path)

        self.loadWaitingAreasFromShapefile(waiting_areas_pos_path)
        self.addWaitingAreasGeometryFromShapefile(waiting_areas_path)

        self.loadCensusAreasFromShapefile(census_areas_path)

    #def loadCrossroadsFromOSMGraph(self,G):
    #    for node, data in G.nodes(data=True):

    #def loadStreetsFromOSMGraph(self,G):

    #def loadCityfromOSM(self, namePlace):
    #    OSMGraph = ox.graph_from_place(namePlace, network_type='all')
    #    self.loadCrossroadsFromOSMGraph(OSMGraph)

    def plotCity(self):
        for building in self.Buildings:
            # print(self.Buildings[building].position)
            # plot(self.Buildings[building].position, 'red')
            print(self.Buildings[building].wtkGeometry)
        for street in self.Streets:
            plot(self.Streets[street].wtkGeometry, 'blue')


class Building:
    def __init__(self, id):
        self.ID = id #that can be the same of the shapefile or autogenerated
        self.addressNumber = None #if any
        self.streetID = None
        self.position = None
        self.wtkGeometry = None
        self.descriptions = []
        self.area = None
        self.risk = None
        self.type = None
        self.nPeople = 0

    def setPosition(self, coordinate):
        self.position = coordinate

    def setStreetID(self, street):
        self.streetID = street


class Crossroad:

    def __init__(self, id):
        self.ID = id
        self.position = None
        self.description = None

    def setPosition(self, coordinate):
        self.position = coordinate


class Street:

    def __init__(self, id, name):
        self.ID = id
        self.name = name
        self.wtkGeometry = None
        self.descriptions = []
        self.width = None
        self.length = None
        self.endpoints = None
        self.listOfBuildings = []
        self.risk = -1

    def divideStreetWithBuildings(self):
        points = []
        buildings = []
        if (self.endpoints):
            points.append((self.endpoints[0][0],self.endpoints[0][1]))
            orig = Point(self.endpoints[0][0],self.endpoints[0][1])
            for building in self.listOfBuildings:
                buildings.append(Point(building.position))
            destinations = MultiPoint(points=list(buildings))

            while (destinations):
                nearest_geoms = nearest_points(orig, destinations)
                nearest_point= nearest_geoms[1]
                points.append((nearest_point.x, nearest_point.y))
                destinations = destinations.difference(nearest_point)
                orig=nearest_point

            points.append((self.endpoints[1][0],self.endpoints[1][1]))
        return points

    def plot(self, color):
        geom = ogr.CreateGeometryFromWkt(self.wtkGeometry)
        plot_geometry(geom, fillcolor=color, alpha=0.2)


class WaitingArea:
    def __init__(self, id):
        self.ID = id
        self.wtkGeometry = None
        self.users_capacity = None
        self.residents_capacity = None
        self.position = None

    def setPosition(self, coordinate):
        self.position = coordinate

class CensusArea:
    def __init__(self, id):
        self.ID = id
        self.wtkGeometry = None
        #self.area = None
        self.residents = None
        self.nBuildings = 0
        self.listOfBuildings = []

def transform_coordinate_to_WGS84(object, srs):
    target_osr = osr.SpatialReference()
    target_osr.ImportFromEPSG(4326)
    transformation = osr.CoordinateTransformation(srs, target_osr)
    object.Transform(transformation)
    return object


def plot(wtk, color):
    geom = ogr.CreateGeometryFromWkt(wtk)
    plot_geometry(geom, fillcolor=color, alpha=0.2)



if __name__ == '__main__':
    Sulmona = City('Sulmona')
    os.chdir('data')  # working directory

    buildings_pos_path = 'buildings/CR01G_EDIFICI_POS.shp'  # position of the buildings' shapefile
    buildings_path = 'buildings/CR01G_EDIFICI.shp'  # polygons that represent the buildings
    # buildings_MSKClass_path='buildings/Classi_MSK.shp'

    crossroads_path = 'streets/CR325G_GZ_STR_POS.shp'
    streets_path = 'streets/CR317G_EL_STR_TRAC.shp'

    waiting_areas_path = 'waiting_areas/Aree di attesa.shp'
    waiting_areas_pos_path = 'waiting_areas/Aree_di_attesa_pos.shp'

    census_areas_path = 'census_areas/sez_cens_2011_SULMO_con_dati.shp'

    Sulmona.loadCrossroadsFromShapefile(crossroads_path)
    Sulmona.loadStreetsFromShapefile(streets_path)
    Sulmona.loadBuildingsFromShapefile(buildings_pos_path)
    Sulmona.addBuildingsGeometryFromShapefile(buildings_path)
    Sulmona.loadWaitingAreasFromShapefile(waiting_areas_pos_path)
    Sulmona.addWaitingAreasGeometryFromShapefile(waiting_areas_path)
    Sulmona.loadCensusAreasFromShapefile(census_areas_path)

    for streetID in Sulmona.Streets:
        street_obj = Sulmona.Streets[streetID]
        street_obj.divideStreetWithBuildings()
        #print(Sulmona.Streets[streetID].descriptions)

    #for building in Sulmona.Buildings:
     #   print (Sulmona.Buildings[building].descriptions)

