diff --git a/backend/api-gateway/Models/LocationData.cs b/backend/api-gateway/Models/LocationData.cs index 61bbfd27..2351ffb2 100644 --- a/backend/api-gateway/Models/LocationData.cs +++ b/backend/api-gateway/Models/LocationData.cs @@ -1,9 +1,13 @@ -namespace APIGateway.Models +using System.Text.Json.Serialization; + +namespace APIGateway.Models { public class LocationDataRequest { - public string DatasetId { get; set; } = string.Empty; - public List Location { get; set; } = new List(); + [JsonPropertyName("datasetId")] + public string DatasetId { get; set; } + [JsonPropertyName("location")] + public List>> Location { get; set; } } public class Coordinate @@ -14,7 +18,7 @@ public class Coordinate public class LocationDataResponse { - public List CurrentDatasetData { get; set; } = new List(); + public List IndividualData { get; set; } = new List(); public List GeneralData { get; set; } = new List(); } diff --git a/backend/metadata-database/init-db.js b/backend/metadata-database/init-db.js index 33c51566..bacfee04 100644 --- a/backend/metadata-database/init-db.js +++ b/backend/metadata-database/init-db.js @@ -86,6 +86,23 @@ const datasets = [ Tables: [], }, }, + { + basicData: { + DatasetId: "building_models", + Name: "Building Models", + ShortDescription: `Simplified 3D building models.`, + Icon: '', + }, + additionalData: { + Icon: '', + Type: "areas", + LongDescription: `The building models have a 3D object of each building plus additional information on its dimentions.`, + MinZoomLevel: 11, + MarkersThreshold: 17, + DisplayProperty: "", + Tables: [], + }, + }, ]; // Iterate over datasets and insert only if DatasetId does not exist diff --git a/backend/src/BIE.Core/BIE.Core.API/ApiHelper.cs b/backend/src/BIE.Core/BIE.Core.API/ApiHelper.cs index f1a362ea..ce132dbe 100644 --- a/backend/src/BIE.Core/BIE.Core.API/ApiHelper.cs +++ b/backend/src/BIE.Core/BIE.Core.API/ApiHelper.cs @@ -5,12 +5,83 @@ using Accord.Math; using BIE.Core.API.Controllers; using BieMetadata; +using NetTopologySuite.Geometries; +using ProjNet.CoordinateSystems; +using ProjNet.CoordinateSystems.Transformations; +using ProjNet.IO.CoordinateSystems; namespace BIE.Core.API; public static class ApiHelper { private static CultureInfo sCultureInfo = new CultureInfo("en-US"); + private static string epsg27700 = @"PROJCS[""North_America_Albers_Equal_Area_Conic"", + GEOGCS[""NAD83"", + DATUM[""North_American_Datum_1983"", + SPHEROID[""GRS 1980"",6378137,298.257222101, + AUTHORITY[""EPSG"",""7019""]], + AUTHORITY[""EPSG"",""6269""]], + PRIMEM[""Greenwich"",0, + AUTHORITY[""EPSG"",""8901""]], + UNIT[""degree"",0.0174532925199433, + AUTHORITY[""EPSG"",""9122""]], + AUTHORITY[""EPSG"",""4269""]], + PROJECTION[""Albers_Conic_Equal_Area""], + PARAMETER[""latitude_of_center"",40], + PARAMETER[""longitude_of_center"",-96], + PARAMETER[""standard_parallel_1"",20], + PARAMETER[""standard_parallel_2"",60], + PARAMETER[""false_easting"",0], + PARAMETER[""false_northing"",0], + UNIT[""metre"",1, + AUTHORITY[""EPSG"",""9001""]], + AXIS[""Easting"",EAST], + AXIS[""Northing"",NORTH], + AUTHORITY[""ESRI"",""102008""] + "; // see http://epsg.io/27700 + private static ICoordinateTransformation mTransformation = new CoordinateTransformationFactory(). + CreateFromCoordinateSystems(GeographicCoordinateSystem.WGS84, + (CoordinateSystem)CoordinateSystemWktReader.Parse(epsg27700)); + + public static double getDistance(double longitude, double latitude, Point p) + { + double[] sourceInWGS84_arr = { longitude, latitude }; + double[] sourceInEPSG27700_arr = mTransformation.MathTransform.Transform(sourceInWGS84_arr); + Coordinate sourceInEPSG27700 = new Coordinate(sourceInEPSG27700_arr[0], sourceInEPSG27700_arr[1]); + + double[] targetPointInWGS84 = { p.Y, p.X }; + double[] targetPointInEpsg27700 = mTransformation.MathTransform.Transform(targetPointInWGS84); + Coordinate targetInEPSG27700 = new Coordinate(targetPointInEpsg27700[0], targetPointInEpsg27700[1]); + var distance = sourceInEPSG27700.Distance(targetInEPSG27700); + return distance; + } + + public static double getArea(Polygon p) + { + // convert each point of the polygon into a new coordinate (revert x and y + // convert it into EPSG27700 + // return area + var coordinates = p.Coordinates; + Console.WriteLine($"coordinates {coordinates}"); + var transformedCoordinates = new Coordinate[coordinates.Length]; + // Transform each coordinate + for (int i = 0; i < coordinates.Length; i++) + { + double[] pointWGS84 = { coordinates[i].Y, coordinates[i].X }; // Switch X and Y + double[] pointEPSG27700 = mTransformation.MathTransform.Transform(pointWGS84); + transformedCoordinates[i] = new Coordinate(pointEPSG27700[0], pointEPSG27700[1]); + Console.WriteLine($"transformedCoordinates[i] {transformedCoordinates[i]}"); + } + var geometryFactory = new GeometryFactory(new PrecisionModel(), 27700); + var transformedPolygon = new Polygon(new LinearRing(transformedCoordinates), geometryFactory); + Console.WriteLine($"area {transformedPolygon.Area}"); + Console.WriteLine($"transformed coords {transformedPolygon.Coordinates}"); + + + + // Return the area of the transformed polygon + return transformedPolygon.Area; + } /// /// Get the Bounding Box from the query parameters. @@ -53,6 +124,38 @@ public static string GetPolygonFromBoundingBox(BoundingBox boundingBox) $" {bottomLong} {bottomLat}))"; } + public static List ConvertToWktPolygons(List>> locations) + { + var culture = new CultureInfo("en-US"); + var wktPolygons = new List(); + + foreach (var polygon in locations) + { + var wktString = "POLYGON(("; + foreach (var point in polygon) + { + if (point.Count != 2) + { + throw new ArgumentException("Each point should have exactly two coordinates."); + } + + var longitude = point[0].ToString(culture); + var latitude = point[1].ToString(culture); + wktString += $"{longitude} {latitude}, "; + } + + // Close the polygon by repeating the first point + var firstPoint = polygon[0]; + var firstLongitude = firstPoint[0].ToString(culture); + var firstLatitude = firstPoint[1].ToString(culture); + wktString += $"{firstLongitude} {firstLatitude}))"; + + wktPolygons.Add(wktString); + } + + return wktPolygons; + } + /// /// Get the polygon of the bounding box given in the queryparameters /// @@ -84,7 +187,7 @@ public static bool BoxIntersection(BoundingBox box1, BoundingBox box2) var right2 = box2.maxX; var top2 = box2.maxY; - Console.WriteLine($"left1: {left1}, left2: {left2}"); + //Console.WriteLine($"left1: {left1}, left2: {left2}"); return !(right1 < left2 || right2 < left1 || top1 < bottom2 || top2 < bottom1); } diff --git a/backend/src/BIE.Core/BIE.Core.API/BIE.Core.API.csproj b/backend/src/BIE.Core/BIE.Core.API/BIE.Core.API.csproj index 37fa139c..fdd411a2 100644 --- a/backend/src/BIE.Core/BIE.Core.API/BIE.Core.API.csproj +++ b/backend/src/BIE.Core/BIE.Core.API/BIE.Core.API.csproj @@ -16,7 +16,9 @@ + + diff --git a/backend/src/BIE.Core/BIE.Core.API/Controllers/DatasetController.cs b/backend/src/BIE.Core/BIE.Core.API/Controllers/DatasetController.cs index 0fc1e955..c720bfe0 100644 --- a/backend/src/BIE.Core/BIE.Core.API/Controllers/DatasetController.cs +++ b/backend/src/BIE.Core/BIE.Core.API/Controllers/DatasetController.cs @@ -15,6 +15,21 @@ using BIE.Core.API.DatasetHandlers; using BieMetadata; using Microsoft.Extensions.Logging; +using System.Text.Json.Serialization; +using NetTopologySuite.IO; +using NetTopologySuite.Geometries; +using Microsoft.AspNetCore.Http; +using NetTopologySuite; +using ProjNet.CoordinateSystems.Transformations; +using ProjNet.CoordinateSystems; +using ProjNet.IO.CoordinateSystems; +using static System.Collections.Specialized.BitVector32; +using NetTopologySuite.Algorithm; +using MySqlX.XDevAPI.Relational; +using Org.BouncyCastle.Asn1.Cms; +using MySqlX.XDevAPI.Common; +using static MongoDB.Bson.Serialization.Serializers.SerializerHelper; +using Accord.Math; namespace BIE.Core.API.Controllers { @@ -29,6 +44,9 @@ public class DatasetController : Controller private readonly ILogger _logger; private MetadataDbHelper mMetadataDbHelper; + private static readonly WKTReader GeoReader = new(new NtsGeometryServices(new PrecisionModel(), 4326)); + private static readonly GeometryFactory GeometryFactory = new(new PrecisionModel(), 4326); + private MetadataDbHelper MetadataDbHelper { @@ -103,197 +121,336 @@ public ActionResult GetDatasetViewportData([FromQuery] QueryParameters parameter return Ok(handler.GetDataInsideArea(boundingBox)); } - private ActionResult GetChargingStations(QueryParameters parameters) + + + /// + /// Loads the location data for the given point or polygon. + /// + /// Contains the current dataset id and the list of coordinates. + /// In case of a single point a list with a single element. + /// Data for the specified point/polygon as a list of key/values. + [HttpPut("loadLocationData")] + [ProducesResponseType(typeof(LocationDataResponse), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public IActionResult LoadLocationData([FromBody, Required] LocationDataRequest request) { - _logger.LogInformation("Fetching charging stations with parameters: {parameters}", parameters); + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + try { - // Create polygon WKT from bounding box - var polygonWkt = ApiHelper.GetPolygonFromQueryParameters(parameters); + var generalData = new List(); + var individualData = new List(); + // can still be called even if we have a single point + var polygons = ApiHelper.ConvertToWktPolygons(request.Location); - // SQL Query to find intersecting points - var sqlQuery = $@" - SELECT top 1000 operator, Location.AsTextZM() AS Location - FROM dbo.EV_charging_stations - WHERE Location.STIntersects(geometry::STGeomFromText('{polygonWkt}', 4326)) = 1; - "; - Console.WriteLine(sqlQuery); - // Get data from database - var data = DbHelper.GetData(sqlQuery); + if (request.Location.Count <= 0) + { + return BadRequest("at least a single point has to be given"); + } - // Construct GeoJSON response - var response = new StringBuilder(); - response.Append("{\"type\": \"FeatureCollection\",\n\"features\": ["); + bool onlySinglePoint = request.Location.Count == 1 && request.Location.ElementAt(0).Count == 1; - foreach (var row in data) + Point pointForCalculations = new Point(new Coordinate()); + if (onlySinglePoint) { - var location = row["Location"].ToString(); - // Extract the coordinates from the POINT string - var coordinates = location.Replace("POINT (", "").Replace(")", "").Split(' '); - var longitude = coordinates[0]; - var latitude = coordinates[1]; - - response.Append($@" - {{ - ""type"": ""Feature"", - ""geometry"": {{ - ""type"": ""Point"", - ""coordinates"": [{longitude}, {latitude}] - }}, - ""properties"": {{ - ""name"": ""{row["operator"]}"" - }} - }},"); + var p = request.Location.First().First(); + pointForCalculations = new Point(p.ElementAt(0), p.ElementAt(1)); } - - if (response[response.Length - 1] == ',') + else { - response.Length--; // Remove the trailing comma + // Create a WKTReader instance + WKTReader reader = new WKTReader(); + Geometry geometry = reader.Read(polygons.First()); + if (geometry is Polygon polygon) + pointForCalculations = polygon.Centroid; + } - response.Append("]}"); + // closest charging stations and number of charging stations + var radius = 10000000; // Define the radius as needed + var columns = new HashSet { "operator", "Id", "rated_power_kw" }; + var metadata = MetadataDbHelper.GetMetadata("EV_charging_stations"); + if (metadata != null) + { + var tables = metadata.additionalData.Tables; + if (tables.Count == 1) + { + var chargingStations = getNclosestObjects(tables[0].Name, pointForCalculations.Y, pointForCalculations.X, radius, 3, columns); - return Ok(response.ToString()); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } + // Log chargingStations to inspect the content + foreach (var station in chargingStations) + { + _logger.LogInformation($"Station data: {string.Join(", ", station.Select(kv => $"{kv.Key}: {kv.Value}"))}"); + } + var chargingStationsData = chargingStations.Select(h => new DatasetItem + { + Id = h.ContainsKey("Id") ? h["Id"] : null, + Key = "Charging station: " + (h.ContainsKey("operator") ? h["operator"] : "No operator defined"), + Value = (h.ContainsKey("Distance") ? h["Distance"] + "m" : "-1") + " | " + (h.ContainsKey("rated_power_kw") ? h["rated_power_kw"] + "kw" : "-1"), + MapId = "EV_charging_stations", + }); - private ActionResult GetHouseFootprintsData([FromQuery] QueryParameters parameters) - { - _logger.LogInformation("Fetching house footprints with parameters: {parameters}", parameters); - try - { - DbHelper.CreateDbConnection(); + individualData.AddRange(chargingStationsData); + } + } - // Create polygon WKT from bounding box - var polygonWkt = ApiHelper.GetPolygonFromQueryParameters(parameters); - - // SQL Query to find intersecting points - var sqlQuery = $@" -SELECT top 1000 Location.AsTextZM() AS Location, Location.STGeometryType() AS Type -FROM dbo.Hausumringe_mittelfranken_small -WHERE Location.STIntersects(geometry::STGeomFromText('{polygonWkt}', 4326)) = 1; -"; - Console.WriteLine(sqlQuery); - // Console.WriteLine(command); - var response = "{\"type\": \"FeatureCollection\",\n\"features\": ["; - foreach (var row in DbHelper.GetData(sqlQuery)) + + var housefootprints = new List>(); + if(onlySinglePoint) { - response += $@" -{{ - ""type"": ""Feature"", - ""geometry"": {{ - ""type"": ""{row["Type"]}"", - ""coordinates"": [{QueryParameters.GetPolygonCordinates(row["Location"])}] - }}, - ""properties"": {{ - ""text"": ""{row["Type"]}"" -}} -}},"; + // Additional section for house footprints + columns = new HashSet { "Id" }; + metadata = MetadataDbHelper.GetMetadata("house_footprints"); + if (metadata != null) + { + var p = request.Location.First().First(); + foreach (var table in metadata.additionalData.Tables) + housefootprints.AddRange(getMatchingObjects(table.Name, p.ElementAt(1), p.ElementAt(0), columns)); + + } } + else + { + _logger.LogInformation("Do housefootprints for area"); - if (response.Last() == ',') + foreach (var polygonWkt in polygons) + { + // then go through house footprints + metadata = MetadataDbHelper.GetMetadata("house_footprints"); + if (metadata != null) + { + _logger.LogInformation("metadata from housefootprints retrieved"); + foreach (var table in metadata.additionalData.Tables) + { + var sqlQuery = $"SELECT Id, Location.STAsText() AS Location" + + ApiHelper.FromTableIntersectsPolygon(table.Name, polygonWkt); + _logger.LogInformation(sqlQuery); + + housefootprints.AddRange(DbHelper.GetData(sqlQuery)); + } + } + + } + } + double totalBuildingArea = 0.0; + if (housefootprints.Any()) { - response = response[..^1]; + var housefootprintsData = housefootprints.Select(h => new DatasetItem + { + Id = h.ContainsKey("Id") ? h["Id"] : null, + Key = "House footprints", + Value = h.ContainsKey("Location") ? ApiHelper.getArea(GeoReader.Read(h["Location"]) as Polygon).ToString("0.##") + "m²" : "-1", + MapId = "House footprints", + }); + individualData.AddRange(housefootprintsData); + + long totalCountHouseFootprints = housefootprints.Count(); + long totalCountChargingStations = 0l; + foreach (var hdata in housefootprints) + { + + totalBuildingArea += hdata.ContainsKey("Location") ? ApiHelper.getArea(GeoReader.Read(hdata["Location"]) as Polygon) : 0; + + } + if (!onlySinglePoint) + { + generalData.Add(new DatasetItem + { + Id = "Total number of buildings in area", + Key = "Total number of buildings in area", + Value = totalCountHouseFootprints.ToString(), + MapId = "" + }); + } + generalData.Add(new DatasetItem + { + Id = "Total building", + Key = "Total building area", + Value = totalBuildingArea.ToString("0.##") + "m²", + MapId = "" + }); } - response += "]}"; - _logger.LogInformation("House footprints data fetched successfully."); - return Ok(response); + ////////////// + if (!onlySinglePoint) + { + WKTReader reader = new WKTReader(); + double totalAreaSearchPolygon = 0.0; + foreach(var polygonWkt in polygons) + { + Geometry geometry = reader.Read(polygonWkt); + if (geometry is Polygon polygon) + totalAreaSearchPolygon += ApiHelper.getArea(polygon); + } + generalData.Insert(0, new DatasetItem + { + Id = "Searched area", + Key = "Searched area", + Value = totalAreaSearchPolygon.ToString("0.##") + "m²", + MapId = "" + }); + generalData.Add(new DatasetItem + { + Id = "Potential Area for geothermal use", + Key = "Potential Area for geothermal use", + Value = Math.Max(totalAreaSearchPolygon - totalBuildingArea, 0).ToString("0.##") + "m²", + MapId = "" + }); + } + + LocationDataResponse locationDataResponse = new() + { + IndividualData = individualData, + GeneralData = generalData + }; + + return Ok(locationDataResponse); } - catch (ServiceException se) + catch (Exception ex) { - _logger.LogError(se, "ServiceException occurred while fetching house footprints data."); - return BadRequest(se.Message); + return StatusCode(500, $"Internal server error: {ex.Message}"); } } - /// - /// Loads the location data for the given point or polygon. - /// - /// Contains the current dataset id and the list of coordinates. - /// In case of a single point a list with a single element. - /// Data for the specified point/polygon as a list of key/values. - [HttpPut("loadLocationData")] - [ProducesResponseType(typeof(LocationDataResponse), 200)] - [ProducesResponseType(400)] - [ProducesResponseType(500)] - public IActionResult LoadLocationData([FromBody, Required] LocationDataRequest request) + private IEnumerable> getNclosestObjects(string datasetId, double longitude, double latitude, double radius, int n, IEnumerable columns) { - _logger.LogInformation("Received request to load location data: {request}", request); - - if (!ModelState.IsValid) + string columnsString = string.Join(", ", columns); + + string command = $@" + SELECT TOP {n} + {columnsString}, + Location.STAsText() AS Location + FROM + dbo.{datasetId} + WHERE + geometry::Point({latitude}, {longitude}, 4326).STBuffer({radius}).STIntersects(Location) = 1 + ORDER BY + geometry::Point({latitude}, {longitude}, 4326).STDistance(Location);"; + + var results = DbHelper.GetData(command).ToList(); + + + foreach (var result in results) { - _logger.LogWarning("Invalid model state: {ModelState}", ModelState); - return BadRequest(ModelState); - } + if (result.ContainsKey("Location")) + { + var wkt = result["Location"]; + var point2_wrong = GeoReader.Read(wkt) as Point; + if (point2_wrong != null) + { - try - { - var coordinate = request.Location.FirstOrDefault(); - if (coordinate == null) + var distance = ApiHelper.getDistance(longitude, latitude, point2_wrong); + result["Distance"] = distance.ToString("0.##"); + } + else + { + result["Distance"] = "-1"; + } + } + else { - _logger.LogWarning("Location coordinates are required."); - return BadRequest("Location coordinates are required."); + result["Distance"] = "-1"; } + } - var latitude = coordinate.Latitude; - var longitude = coordinate.Longitude; - var radius = 10000000; // Define the radius as needed - - DbHelper.CreateDbConnection(); + return results; + } - string command = @" - SELECT TOP 5 - Id, - Location.STArea() AS Area, - Location.STAsText() AS Location, - geography::Point({0}, {1}, 4326).STDistance(Location) AS Distance - FROM - dbo.Hausumringe_mittelfranken_small - WHERE - geography::Point({0}, {1}, 4326).STBuffer({2}).STIntersects(Location) = 1 - ORDER BY - geography::Point({0}, {1}, 4326).STDistance(Location);"; - string formattedQuery = string.Format(command, latitude, longitude, radius); - var response = new LocationDataResponse - { - CurrentDatasetData = new List() - }; + //private long getObjectCount(string datasetId, ) + private IEnumerable> getMatchingObjects( + string datasetId, + double[] longitude, + double[] latitude, + IEnumerable columns) + { + return new List>(); + } - foreach (var row in DbHelper.GetData(formattedQuery, 600)) + private IEnumerable> getMatchingObjects( + string datasetId, + double longitude, + double latitude, + IEnumerable columns) + { + // Convert the columns set to a comma-separated string + string columnsString = string.Join(", ", columns); + + // Define the SQL command with the columns string + string command = $@" + SELECT TOP 1000 + {columnsString}, + Location.STAsText() AS Location + FROM + dbo.{datasetId} + WHERE + Location.STIntersects(geometry::Point({latitude}, {longitude}, 4326)) = 1 + ORDER BY + geometry::Point({latitude}, {longitude}, 4326).STDistance(Location);"; + + _logger.LogInformation(command); + + // Get data from the database + var results = DbHelper.GetData(command).ToList(); + + foreach (var result in results) + { + if (result.ContainsKey("Location")) { - var area = row["Area"]; - var distance = row["Distance"]; - var location = row["Location"]; - - response.CurrentDatasetData.Add(new DatasetItem + var wkt = result["Location"]; + var polygon = GeoReader.Read(wkt) as Polygon; + if (polygon != null) { - Id = row["Id"].ToString(), - Key = location, - Value = $"{distance}m, {area}m^2", - MapId = "house_footprints" - }); + result["Area"] = ApiHelper.getArea(polygon).ToString("0.##"); + } + else + result["Area"] = "-1"; + } + else + result["Area"] = "-1 no location"; + + } - _logger.LogInformation("Location data loaded successfully."); - return Ok(response); + return results; + } + + + public static List> ClusterData(List data, int numberOfClusters) + { + var centroids = data.Select(d => QueryParameters.CalculateCentroid(d.Coordinates)).ToArray(); + var kmeans = new KMeans(numberOfClusters); + var clusters = kmeans.Learn(centroids); + int[] labels = clusters.Decide(centroids); + + for (int i = 0; i < labels.Length; i++) + { + data[i].ClusterId = labels[i]; } - catch (Exception ex) + + var clusteredData = new List>(); + for (int i = 0; i < numberOfClusters; i++) { - _logger.LogError(ex, "Exception occurred while loading location data."); - return StatusCode(500, $"Internal server error: {ex.Message}"); + clusteredData.Add(new List()); } + + for (int i = 0; i < labels.Length; i++) + { + clusteredData[labels[i]].Add(data[i]); + } + + return clusteredData; } + /// /// WIP: DO NOT USE, Get a record /// @@ -500,6 +657,43 @@ public static double[] CalculateCentroid(List coordinates) return new double[] { centroidX / pointCount, centroidY / pointCount }; } + public static List> GetEVChargingStations(double latitude, double longitude, int buffer) + { + string command = @" + SELECT TOP 5 + Id, + Location.STAsText() AS Location, + geometry::Point({0}, {1}, 4326).STDistance(Location) AS Distance + FROM + dbo.EV_charging_stations + WHERE + geometry::Point({0}, {1}, 4326).STBuffer({2}).STIntersects(Location) = 1 + ORDER BY + geometry::Point({0}, {1}, 4326).STDistance(Location);"; + + string formattedQuery = string.Format(command, latitude, longitude, buffer); + return DbHelper.GetData(formattedQuery).ToList(); + } + + public static List> GetHouseFootprints(double latitude, double longitude, int radius) + { + string command = @" + SELECT TOP 5 + Id, + Location.STArea() AS Area, + Location.STAsText() AS Location, + geometry::Point({0}, {1}, 4326).STDistance(Location) AS Distance + FROM + dbo.Hausumringe_mittelfranken_small + WHERE + geometry::Point({0}, {1}, 4326).STBuffer({2}).STIntersects(Location) = 1 + ORDER BY + geometry::Point({0}, {1}, 4326).STDistance(Location);"; + + string formattedQuery = string.Format(command, latitude, longitude, radius); + return DbHelper.GetData(formattedQuery).ToList(); + } + public static List> ClusterData(List data, int numberOfClusters) { var centroids = data.Select(d => CalculateCentroid(d.Coordinates)).ToArray(); @@ -566,19 +760,15 @@ public static GeoJsonFeatureCollection ConvertToGeoJson(List> public class LocationDataRequest { + [JsonPropertyName("datasetId")] public string DatasetId { get; set; } - public List Location { get; set; } - } - - public class Coordinate - { - public double Latitude { get; set; } - public double Longitude { get; set; } + [JsonPropertyName("location")] + public List>> Location { get; set; } } public class LocationDataResponse { - public List CurrentDatasetData { get; set; } + public List IndividualData { get; set; } public List GeneralData { get; set; } } diff --git a/backend/src/BIE.Core/BIE.Core.API/Properties/launchsettings.json b/backend/src/BIE.Core/BIE.Core.API/Properties/launchSettings.json similarity index 69% rename from backend/src/BIE.Core/BIE.Core.API/Properties/launchsettings.json rename to backend/src/BIE.Core/BIE.Core.API/Properties/launchSettings.json index 363f07d2..914174b4 100644 --- a/backend/src/BIE.Core/BIE.Core.API/Properties/launchsettings.json +++ b/backend/src/BIE.Core/BIE.Core.API/Properties/launchSettings.json @@ -2,15 +2,17 @@ "profiles": { "BIE.Core.API": { "commandName": "Project", + "launchBrowser": true, "environmentVariables": { "DB_SERVER": "localhost", "DB_NAME": "BIEDB", "DB_PASSWORD": "MyPass@1234", - "DB_USERNAME": "db_user1", "DB_TYPE": "SQL", - "TRUSTED": "True", + "TRUSTED": "False", + "DB_USERNAME": "db_user1", "METADATA_DB_URL": "localhost:27017" - } + }, + "applicationUrl": "https://localhost:63103;http://localhost:63104" } } -} \ No newline at end of file +} diff --git a/backend/src/BIE.DataPipeline/DbHelper.cs b/backend/src/BIE.DataPipeline/DbHelper.cs index 629e3180..40e8e1eb 100644 --- a/backend/src/BIE.DataPipeline/DbHelper.cs +++ b/backend/src/BIE.DataPipeline/DbHelper.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Runtime.InteropServices; using System.Text; +using System.Text.RegularExpressions; using BIE.Data; using BIE.DataPipeline.Import; using BieMetadata; @@ -20,6 +21,7 @@ public DbHelper() { ConfigureEnvironmentVariables(); mStringBuilder = new StringBuilder(); + cityGMLStringBuilder = new StringBuilder(); } /// @@ -45,6 +47,10 @@ public static bool CanSkip(DataSourceDescription description) if (description.options.if_table_exists == InsertBehaviour.replace) { Console.WriteLine($"Dropping table {description.table_name} if it exists."); + if(description.source.data_format == "CITYGML") + { + DropCityGMLTable(description.table_name); + } db.ExecuteScalar(db.CreateCommand($"DROP TABLE IF EXISTS {description.table_name}")); } @@ -285,7 +291,8 @@ IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '{desc BEGIN CREATE TABLE {description.table_name} ( Id INT PRIMARY KEY IDENTITY(1,1), - Location GEOMETRY + Location GEOMETRY, + Area FLOAT ); END"; } @@ -303,6 +310,9 @@ Id INT PRIMARY KEY IDENTITY(1,1), DistrictKey VARCHAR(255), CheckDate DATE, GroundArea FLOAT, + BuildingWallHeight FLOAT, + LivingArea FLOAT, + RoofArea FLOAT, ); END"; } @@ -367,6 +377,10 @@ private void ExecuteInsert() throw; } + if(cityGMLStringBuilder.Length > 0) + { + PushRoofData(); + } mCount = 0; mStringBuilder.Clear(); mStringBuilder.Append(mInputQueryString); @@ -399,5 +413,107 @@ private void ConfigureEnvironmentVariables() dbPassword, dbTrusted); } + + #region Extra functions for CityGML additional tables + + private readonly StringBuilder cityGMLStringBuilder; + + private int cityGMLCount; + private string cityGMLInputQueryString; + public bool CreatCityGMLRoofTable(string mainTableName) + { + + try + { + Console.WriteLine("Creating Roof Table..."); + var db = Database.Instance; + + string tableName = string.Format("{0}_roofs", mainTableName); + string columnNames = "Building_id, Area, Orientation, Angle"; + cityGMLInputQueryString = $"INSERT INTO {tableName} ( {columnNames} ) VALUES "; + + var query = $@" +IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '{tableName}') +BEGIN + CREATE TABLE {tableName} ( + Id INT PRIMARY KEY IDENTITY(1,1), + Building_Id INT NOT NULL, + Area FLOAT, + Orientation FLOAT, + Angle FLOAT, + FOREIGN KEY (Building_Id) REFERENCES {mainTableName}(Id) + ); +END"; + + var cmd = db.CreateCommand(query); + db.Execute(cmd); + + Console.WriteLine("Table created."); + + return true; + } + catch (Exception e) + { + Console.WriteLine("Error while creating Table:"); + Console.Error.WriteLine(e); + return false; + } + } + + public void InsertRoofData(string building, string area, string orientation, string angle) + { + if (cityGMLCount > 0) + { + cityGMLStringBuilder.Append(','); + } + else + { + cityGMLStringBuilder.Clear(); + cityGMLStringBuilder.Append(cityGMLInputQueryString); + } + + cityGMLStringBuilder.Append($"({building}, {area}, {orientation}, {angle})"); + + cityGMLCount++; + + if(cityGMLCount >= MaxCount) + { + mCount = MaxCount; + } + } + + private void PushRoofData() + { + cityGMLStringBuilder.Append(';'); + var query = cityGMLStringBuilder.ToString(); + + try + { + var db = Database.Instance; + var cmd = db.CreateCommand(query); + db.Execute(cmd); + } + catch (Exception e) + { + Console.WriteLine("Failed with:\n" + query); + throw; + } + + cityGMLCount = 0; + cityGMLStringBuilder.Clear(); + cityGMLStringBuilder.Append(cityGMLInputQueryString); + } + + private static void DropCityGMLTable(string tableName) + { + var db = Database.Instance; + string roofTableName = string.Format("{0}_roofs", tableName); + + db.ExecuteScalar(db.CreateCommand($"DROP TABLE IF EXISTS {roofTableName}")); + db.ExecuteScalar(db.CreateCommand($"DROP TABLE IF EXISTS {tableName}")); + } + + #endregion + } } \ No newline at end of file diff --git a/backend/src/BIE.DataPipeline/Import/CityGmlImporter.cs b/backend/src/BIE.DataPipeline/Import/CityGmlImporter.cs index c2faadac..d559e075 100644 --- a/backend/src/BIE.DataPipeline/Import/CityGmlImporter.cs +++ b/backend/src/BIE.DataPipeline/Import/CityGmlImporter.cs @@ -21,10 +21,12 @@ class CityGmlImporter : IImporter private XmlNodeList buildingNodes; private XmlNamespaceManager nsmgr; private CultureInfo culture = new CultureInfo("en-US"); + private DbHelper db; + private bool firstRead = true; private readonly ICoordinateTransformation? mTransformation; - public CityGmlImporter(DataSourceDescription description) + public CityGmlImporter(DataSourceDescription description, DbHelper db) { // Define the source and target coordinate systems var utmZone32 = CoordinateSystemWktReader @@ -41,6 +43,7 @@ public CityGmlImporter(DataSourceDescription description) mTransformation = new CoordinateTransformationFactory().CreateFromCoordinateSystems((CoordinateSystem)utmZone32, wgs84); this.description = description; + this.db = db; this.buildingNodes = ReadBuildings(); } @@ -85,6 +88,14 @@ public XmlNodeList ReadBuildings() /// SQL Polygon, xml data public bool ReadLine(out string nextLine) { + if (firstRead) + { + //create roof table during the first read process. + //Cant be done in the constructor because the main table does not exist jet. + db.CreatCityGMLRoofTable(description.table_name); + firstRead = false; + } + if(this.buildingIndex < buildingNodes.Count) { XmlNode buildingNode = buildingNodes[this.buildingIndex]; @@ -111,6 +122,9 @@ public bool ReadLine(out string nextLine) string districtKey = GetBuildingDistrictKey(buildingNode); string checkDate = GetBuildingCheckDate(buildingNode); float groundArea = GetBuildingGroundArea(buildingNode); + float buildingWallHeight = GetBuildingWallHeight(buildingNode); + float livingArea = GetLivingArea(buildingNode, groundArea, buildingWallHeight); + float roofArea = GetRoofArea(buildingNode); nextLine = $"geography::STGeomFromText('{geometry.AsText()}', 4326)"; nextLine += string.Format(",'{0}'", buildingNode.InnerXml); @@ -118,6 +132,9 @@ public bool ReadLine(out string nextLine) nextLine += string.Format(",'{0}'", districtKey); nextLine += string.Format(",'{0}'", checkDate); nextLine += string.Format(",{0}", groundArea.ToString(culture)); + nextLine += string.Format(",{0}", buildingWallHeight.ToString(culture)); + nextLine += string.Format(",{0}", livingArea.ToString(culture)); + nextLine += string.Format(",{0}", roofArea.ToString(culture)); this.buildingIndex++; return true; @@ -275,6 +292,70 @@ private float GetBuildingGroundArea(XmlNode buildingNode) return res; } + private float GetBuildingWallHeight(XmlNode buildingNode) + { + float hoeheGrund = GetStringAttributeValue(buildingNode, "HoeheGrund"); + float niedrigsteTraufeDesGebaeudes = GetStringAttributeValue(buildingNode, "NiedrigsteTraufeDesGebaeudes"); + if(hoeheGrund == -1 || niedrigsteTraufeDesGebaeudes == -1) + { + return -1; + } + else + { + return niedrigsteTraufeDesGebaeudes - hoeheGrund; + } + } + + private float GetLivingArea(XmlNode buildingNode, float groundArea, float buildingWallHeight) + { + if(groundArea == -1 || buildingWallHeight == -1) + { + return -1; + } + + const float averageFloorHeight = 2.85f; + return groundArea * (float)Math.Ceiling(buildingWallHeight / averageFloorHeight); + } + + private float GetRoofArea(XmlNode buildingNode) + { + XmlNodeList roofNodes = buildingNode.SelectNodes(".//bldg:RoofSurface", this.nsmgr); + float roofArea = 0; + foreach(XmlNode roofNode in roofNodes) + { + float roofTileArea = GetStringAttributeValue(roofNode, "Flaeche"); + float roofTileAngle = GetStringAttributeValue(roofNode, "Dachneigung"); + float roofTileOrientation = GetStringAttributeValue(roofNode, "Dachorientierung"); + db.InsertRoofData((this.buildingIndex + 1).ToString(culture), roofTileArea.ToString(culture), roofTileOrientation.ToString(culture), roofTileAngle.ToString(culture)); + if(roofTileArea != -1) + { + roofArea += roofTileArea; + } + } + + return roofArea; + } + + private float GetStringAttributeValue(XmlNode buildingNode, string name, float defaultValue = -1) + { + XmlNode groundHeightNode = buildingNode.SelectSingleNode(".//gen:stringAttribute[@name='" + name + "']/gen:value", this.nsmgr); + + if (groundHeightNode == null) + { + Console.WriteLine("No " + name + " value"); + return defaultValue; + } + + float result = 0; + if (!float.TryParse(groundHeightNode.InnerText, NumberStyles.Any, culture, out result)) + { + Console.WriteLine("Unable to parse " + name); + return defaultValue; + } + + return result; + } + private float ParseArea(XmlNode node) { XmlNode areaNode = node.SelectSingleNode(".//gen:stringAttribute[@name='Flaeche']/gen:value", this.nsmgr); diff --git a/backend/src/BIE.DataPipeline/Import/ShapeImporter.cs b/backend/src/BIE.DataPipeline/Import/ShapeImporter.cs index 1aec5813..c9054012 100644 --- a/backend/src/BIE.DataPipeline/Import/ShapeImporter.cs +++ b/backend/src/BIE.DataPipeline/Import/ShapeImporter.cs @@ -126,10 +126,51 @@ public bool ReadLine(out string nextLine) var geometry = mParser.Geometry; geometry = ConvertUtmToLatLong(geometry); - nextLine = $"GEOMETRY::STGeomFromText('{geometry.AsText()}', 4326)"; + var area = CalculateAreaInSquareMeters(geometry); + + nextLine = $"GEOMETRY::STGeomFromText('{geometry.AsText()}', 4326),"+area; return true; } + /// + /// Calculates Area of Polygon + /// + /// + /// + private double CalculateAreaInSquareMeters(Geometry geometry) + { + // Define the coordinate systems + var wgs84 = GeographicCoordinateSystem.WGS84; + var utm32 = ProjectedCoordinateSystem.WGS84_UTM(32, true); // UTM zone 32 for the given coordinates + + var ctfac = new CoordinateTransformationFactory(); + var transform = ctfac.CreateFromCoordinateSystems(wgs84, utm32); + + // Transform the coordinates + var coordinates = geometry.Coordinates; + var transformedCoordinates = new Coordinate[coordinates.Length]; + + for (int i = 0; i < coordinates.Length; i++) + { + double[] transformed = transform.MathTransform.Transform(new double[] { coordinates[i].X, coordinates[i].Y }); + transformedCoordinates[i] = new Coordinate(transformed[0], transformed[1]); + } + + // Ensure the polygon is closed + if (!transformedCoordinates[0].Equals2D(transformedCoordinates[transformedCoordinates.Length - 1])) + { + Array.Resize(ref transformedCoordinates, transformedCoordinates.Length + 1); + transformedCoordinates[transformedCoordinates.Length - 1] = transformedCoordinates[0]; + } + + // Create a polygon with transformed coordinates + var geometryFactory = new GeometryFactory(); + var transformedPolygon = geometryFactory.CreatePolygon(transformedCoordinates); + + // Calculate and return the area + return transformedPolygon.Area; + } + private string[] ReadFileHeader() { diff --git a/backend/src/BIE.DataPipeline/Program.cs b/backend/src/BIE.DataPipeline/Program.cs index 68175ee8..96c7908f 100644 --- a/backend/src/BIE.DataPipeline/Program.cs +++ b/backend/src/BIE.DataPipeline/Program.cs @@ -77,11 +77,11 @@ case "SHAPE": importer = new ShapeImporter(description); - dbHelper.SetInfo(description.table_name, "Location"); + dbHelper.SetInfo(description.table_name, "Location ,Area"); break; case "CITYGML": - importer = new CityGmlImporter(description); - dbHelper.SetInfo(description.table_name, "Location, XmlData, GroundHeight, DistrictKey, CheckDate, GroundArea"); + importer = new CityGmlImporter(description, dbHelper); + dbHelper.SetInfo(description.table_name, "Location, XmlData, GroundHeight, DistrictKey, CheckDate, GroundArea, BuildingWallHeight, LivingArea, RoofArea"); break; default: diff --git a/backend/src/BIE.DataPipeline/building_models.py b/backend/src/BIE.DataPipeline/building_models.py new file mode 100644 index 00000000..f2bec385 --- /dev/null +++ b/backend/src/BIE.DataPipeline/building_models.py @@ -0,0 +1,89 @@ +""" +This scripts generates all yaml files in a given meta4 file and saves them in a folder named building_models_yaml_file. +The path to the meta4 file has to be set in this script. +""" +import xml.etree.ElementTree as ET +#pip install pyyaml +import yaml +import os +file_path = 'This should be the full path the the meta4 file eg: C:\\folder\\test.meta4' + +def extract_file_info(xml_data): + # Parse the XML data + root = ET.fromstring(xml_data) + + # Define the namespace + ns = {'ml': 'urn:ietf:params:xml:ns:metalink'} + + # Extract file name and the first URL for each file + file_info = [] + for file in root.findall('ml:file', ns): + name = file.get('name') + url = file.find('ml:url', ns).text + file_info.append((name, url)) + + return file_info + +def create_folder(path): + try: + # Create the folder + os.makedirs(path, exist_ok=True) + print(f"Folder created successfully at: {path}") + except Exception as e: + print(f"An error occurred: {e}") + +def create_yaml_file(file_path, name, url): + table_name = "building_models_" + name + + # Data to be written to the JSON file + data = { + "source": { + "type": "URL", + "location": url, + "data_format": "CITYGML" + }, + "options": { + "skip_lines": 0, + "discard_null_rows": False, + "if_table_exists": "skip" + }, + "table_name": table_name, + "table_cols": [], + "dataset": "building_models" + } + + target_file = file_path + "\\" + table_name + ".yaml" + + # Write data to a JSON file + with open(target_file, 'w', encoding='utf-8') as json_file: + yaml.dump(data, json_file, indent=4) + +def get_script_location(): + # Get the absolute path of the current script + script_path = os.path.abspath(__file__) + # Get the directory containing the script + script_dir = os.path.dirname(script_path) + return script_path, script_dir + +def remove_file_extension(file_name): + # Split the file name into a name and extension + name, _ = os.path.splitext(file_name) + return name + +# Get the location of the current script +script_path, script_dir = get_script_location() + + +target_folder_path = script_dir + "\\building_models_yaml_file" +with open(file_path, 'r', encoding='utf-8') as file: + xml_data = file.read() +file_info = extract_file_info(xml_data) +full_path = os.path.abspath(target_folder_path) +print(f"Full path: {full_path}") +create_folder(target_folder_path) +i = 0 +for name, url in file_info: + i = i + 1 + print(i) + #print(f"File Name: {name}\nFirst URL: {url}\n") + create_yaml_file(target_folder_path, remove_file_extension(name), url) \ No newline at end of file diff --git a/backend/src/BIE.DataPipeline/yaml/common/building_models.yaml b/backend/src/BIE.DataPipeline/yaml/common/building_models.yaml deleted file mode 100644 index dc53fa2e..00000000 --- a/backend/src/BIE.DataPipeline/yaml/common/building_models.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# describe the source -source: - # link | filepath - type: URL - location: https://download1.bayernwolke.de/a/lod2/citygml/652_5496.gml - data_format: CITYGML -options: - # skip lines at the beginning - skip_lines: 0 - # discard any rows that have null values - discard_null_rows: false - # how to deal with existing table. Options: ignore, replace, skip (default). - if_table_exists: replace -table_name: building_models - -table_cols: \ No newline at end of file diff --git a/backend/src/BIE.DataPipeline/yaml/common/building_models_646_5492.yaml b/backend/src/BIE.DataPipeline/yaml/common/building_models_646_5492.yaml new file mode 100644 index 00000000..045ec1c6 --- /dev/null +++ b/backend/src/BIE.DataPipeline/yaml/common/building_models_646_5492.yaml @@ -0,0 +1,11 @@ +dataset: building_models +options: + discard_null_rows: false + if_table_exists: skip + skip_lines: 0 +source: + data_format: CITYGML + location: https://download1.bayernwolke.de/a/lod2/citygml/646_5492.gml + type: URL +table_cols: [] +table_name: building_models_646_5492 diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Amberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Amberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Amberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Amberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Ansbach.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Ansbach.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Ansbach.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Ansbach.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Aschaffenburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Aschaffenburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Aschaffenburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Aschaffenburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Augsburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Augsburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Augsburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Augsburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Bamberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Bamberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Bamberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Bamberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Bayreuth.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Bayreuth.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Bayreuth.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Bayreuth.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Coburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Coburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Coburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Coburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Erlangen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Erlangen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Erlangen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Erlangen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Hof.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Hof.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Hof.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Hof.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Kaufbeuren.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Kaufbeuren.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Kaufbeuren.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Kaufbeuren.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Kempten_Allgaeu.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Kempten_Allgaeu.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Kempten_Allgaeu.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Kempten_Allgaeu.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Landshut.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Landshut.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Landshut.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Landshut.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Memmingen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Memmingen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Memmingen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Memmingen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Muenchen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Muenchen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Muenchen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Muenchen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Passau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Passau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Passau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Passau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Regensburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Regensburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Regensburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Regensburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Rosenheim.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Rosenheim.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Rosenheim.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Rosenheim.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Schweinfurt.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Schweinfurt.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Schweinfurt.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Schweinfurt.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Straubing.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Straubing.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Straubing.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Straubing.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Weiden_i_d_OPf_.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Weiden_i_d_OPf_.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Weiden_i_d_OPf_.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Weiden_i_d_OPf_.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Wuerzburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Wuerzburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Kreisfreie_Stadt_Wuerzburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Kreisfreie_Stadt_Wuerzburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Aichach_Friedberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Aichach_Friedberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Aichach_Friedberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Aichach_Friedberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Altoetting.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Altoetting.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Altoetting.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Altoetting.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Amberg_Sulzbach.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Amberg_Sulzbach.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Amberg_Sulzbach.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Amberg_Sulzbach.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Ansbach.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Ansbach.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Ansbach.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Ansbach.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Aschaffenburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Aschaffenburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Aschaffenburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Aschaffenburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Augsburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Augsburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Augsburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Augsburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bad_Kissingen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bad_Kissingen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bad_Kissingen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bad_Kissingen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bad_Toelz_Wolfratshausen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bad_Toelz_Wolfratshausen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bad_Toelz_Wolfratshausen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bad_Toelz_Wolfratshausen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bamberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bamberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bamberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bamberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bayreuth.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bayreuth.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Bayreuth.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Bayreuth.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Berchtesgadener_Land.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Berchtesgadener_Land.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Berchtesgadener_Land.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Berchtesgadener_Land.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Cham.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Cham.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Cham.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Cham.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Coburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Coburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Coburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Coburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Dachau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Dachau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Dachau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Dachau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Deggendorf.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Deggendorf.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Deggendorf.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Deggendorf.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Dillingen_a_d_Donau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Dillingen_a_d_Donau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Dillingen_a_d_Donau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Dillingen_a_d_Donau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Dingolfing_Landau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Dingolfing_Landau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Dingolfing_Landau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Dingolfing_Landau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Donau_Ries.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Donau_Ries.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Donau_Ries.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Donau_Ries.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Ebersberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Ebersberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Ebersberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Ebersberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Eichstaett.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Eichstaett.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Eichstaett.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Eichstaett.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Erlangen_Hoechstadt.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Erlangen_Hoechstadt.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Erlangen_Hoechstadt.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Erlangen_Hoechstadt.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Forchheim.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Forchheim.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Forchheim.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Forchheim.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Freising.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Freising.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Freising.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Freising.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Freyung_Grafenau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Freyung_Grafenau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Freyung_Grafenau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Freyung_Grafenau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Fuerstenfeldbruck.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Fuerstenfeldbruck.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Fuerstenfeldbruck.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Fuerstenfeldbruck.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Fuerth.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Fuerth.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Fuerth.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Fuerth.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Garmisch_Partenkirchen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Garmisch_Partenkirchen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Garmisch_Partenkirchen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Garmisch_Partenkirchen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Guenzburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Guenzburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Guenzburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Guenzburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Hassberge.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Hassberge.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Hassberge.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Hassberge.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Hof.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Hof.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Hof.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Hof.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kelheim.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kelheim.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kelheim.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kelheim.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kitzingen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kitzingen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kitzingen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kitzingen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kronach.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kronach.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kronach.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kronach.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kulmbach.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kulmbach.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Kulmbach.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Kulmbach.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Landsberg_am_Lech.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Landsberg_am_Lech.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Landsberg_am_Lech.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Landsberg_am_Lech.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Landshut.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Landshut.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Landshut.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Landshut.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Lichtenfels.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Lichtenfels.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Lichtenfels.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Lichtenfels.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Lindau_Bodensee.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Lindau_Bodensee.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Lindau_Bodensee.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Lindau_Bodensee.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Main_Spessart.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Main_Spessart.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Main_Spessart.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Main_Spessart.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Miltenberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Miltenberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Miltenberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Miltenberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Muehldorf_a_Inn.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Muehldorf_a_Inn.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Muehldorf_a_Inn.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Muehldorf_a_Inn.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Muenchen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Muenchen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Muenchen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Muenchen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neu_Ulm.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neu_Ulm.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neu_Ulm.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neu_Ulm.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neuburg_Schrobenhausen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neuburg_Schrobenhausen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neuburg_Schrobenhausen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neuburg_Schrobenhausen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neumarkt_i_d_OPf_.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neumarkt_i_d_OPf_.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neumarkt_i_d_OPf_.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neumarkt_i_d_OPf_.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neustadt_a_d_Aisch_Bad_Windsheim.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neustadt_a_d_Aisch_Bad_Windsheim.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neustadt_a_d_Aisch_Bad_Windsheim.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neustadt_a_d_Aisch_Bad_Windsheim.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neustadt_a_d_Waldnaab.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neustadt_a_d_Waldnaab.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Neustadt_a_d_Waldnaab.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Neustadt_a_d_Waldnaab.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Nuernberger_Land.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Nuernberger_Land.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Nuernberger_Land.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Nuernberger_Land.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Oberallgaeu.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Oberallgaeu.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Oberallgaeu.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Oberallgaeu.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Ostallgaeu.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Ostallgaeu.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Ostallgaeu.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Ostallgaeu.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Passau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Passau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Passau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Passau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Pfaffenhofen_a_d_Ilm.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Pfaffenhofen_a_d_Ilm.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Pfaffenhofen_a_d_Ilm.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Pfaffenhofen_a_d_Ilm.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Regen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Regen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Regen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Regen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Regensburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Regensburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Regensburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Regensburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Rhoen_Grabfeld.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Rhoen_Grabfeld.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Rhoen_Grabfeld.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Rhoen_Grabfeld.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Rosenheim.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Rosenheim.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Rosenheim.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Rosenheim.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Roth.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Roth.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Roth.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Roth.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Rottal_Inn.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Rottal_Inn.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Rottal_Inn.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Rottal_Inn.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Schwandorf.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Schwandorf.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Schwandorf.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Schwandorf.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Schweinfurt.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Schweinfurt.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Schweinfurt.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Schweinfurt.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Starnberg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Starnberg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Starnberg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Starnberg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Straubing_Bogen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Straubing_Bogen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Straubing_Bogen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Straubing_Bogen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Tirschenreuth.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Tirschenreuth.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Tirschenreuth.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Tirschenreuth.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Traunstein.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Traunstein.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Traunstein.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Traunstein.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Unterallgaeu.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Unterallgaeu.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Unterallgaeu.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Unterallgaeu.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Weilheim_Schongau.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Weilheim_Schongau.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Weilheim_Schongau.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Weilheim_Schongau.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Weissenburg_Gunzenhausen.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Weissenburg_Gunzenhausen.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Weissenburg_Gunzenhausen.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Weissenburg_Gunzenhausen.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Wuerzburg.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Wuerzburg.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Wuerzburg.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Wuerzburg.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Wunsiedel_i_Fichtelgebirge.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Wunsiedel_i_Fichtelgebirge.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/actual_use_Landkreis_Wunsiedel_i_Fichtelgebirge.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/actual_use_Landkreis_Wunsiedel_i_Fichtelgebirge.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_mittelfranken.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_mittelfranken.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_mittelfranken.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_mittelfranken.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_niederbayern.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_niederbayern.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_niederbayern.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_niederbayern.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_oberbayern.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_oberbayern.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_oberbayern.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_oberbayern.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_oberfranken.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_oberfranken.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_oberfranken.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_oberfranken.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_oberpfalz.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_oberpfalz.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_oberpfalz.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_oberpfalz.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_schwaben.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_schwaben.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_schwaben.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_schwaben.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/hausumringe_unterfranken.yaml b/backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_unterfranken.yaml similarity index 100% rename from backend/src/BIE.DataPipeline/yaml/production/hausumringe_unterfranken.yaml rename to backend/src/BIE.DataPipeline/yaml/not_used/hausumringe_unterfranken.yaml diff --git a/backend/src/BIE.DataPipeline/yaml/production/building_models_fauTechFak.yaml b/backend/src/BIE.DataPipeline/yaml/production/building_models_fauTechFak.yaml deleted file mode 100644 index 5c141727..00000000 --- a/backend/src/BIE.DataPipeline/yaml/production/building_models_fauTechFak.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# describe the source -source: - # link | filepath - type: - location: https://download1.bayernwolke.de/a/lod2/citygml/646_5492.gml - data_format: CITYGML -options: - # skip lines at the beginning - skip_lines: 0 - # discard any rows that have null values - discard_null_rows: false - # how to deal with existing table. Options: ignore, replace, skip (default). - if_table_exists: replace -table_name: building_models - -table_cols: \ No newline at end of file diff --git a/backend/src/BIE.DataPipeline/yaml/production/keep_me.txt b/backend/src/BIE.DataPipeline/yaml/production/keep_me.txt new file mode 100644 index 00000000..60ac58b0 --- /dev/null +++ b/backend/src/BIE.DataPipeline/yaml/production/keep_me.txt @@ -0,0 +1,3 @@ +A simple file to keep the `production` folder inside git. +Replace this file with .yaml files to be loaded in the production environment. +All not used .yaml files are stored in the `yaml/not_used` folder. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index bb897402..d92f8d11 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,6 +67,7 @@ services: - DB_TYPE=${SQL_DB_TYPE} - TRUSTED=${SQL_TRUSTED} - METADATA_DB_URL=metadata-database:${METADATA_DATABASE_PORT} + - ENVIRONMENT_STAGE=${ENVIRONMENT_STAGE} container_name: datapipeline networks: - bie-network diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a4136a56..7eeb9bd5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,12 +18,14 @@ "@phosphor-icons/react": "^2.1.5", "@types/autosuggest-highlight": "^3.2.3", "@types/geojson": "^7946.0.14", + "@types/leaflet-draw": "^1.0.11", "@types/leaflet.markercluster": "^1.5.4", "@types/proj4leaflet": "^1.0.10", "autosuggest-highlight": "^3.3.4", "axios": "^1.7.2", "geojson": "^0.5.0", "leaflet": "^1.9.4", + "leaflet-draw": "^1.0.4", "leaflet-geosearch": "^4.0.0", "leaflet.markercluster": "^1.5.3", "proj4leaflet": "^1.0.2", @@ -1809,6 +1811,14 @@ "@types/geojson": "*" } }, + "node_modules/@types/leaflet-draw": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/leaflet-draw/-/leaflet-draw-1.0.11.tgz", + "integrity": "sha512-dyedtNm3aSmnpi6FM6VSl28cQuvP+MD7pgpXyO3Q1ZOCvrJKmzaDq0P3YZTnnBs61fQCKSnNYmbvCkDgFT9FHQ==", + "dependencies": { + "@types/leaflet": "*" + } + }, "node_modules/@types/leaflet.markercluster": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.4.tgz", @@ -3295,6 +3305,11 @@ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" }, + "node_modules/leaflet-draw": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/leaflet-draw/-/leaflet-draw-1.0.4.tgz", + "integrity": "sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ==" + }, "node_modules/leaflet-geosearch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/leaflet-geosearch/-/leaflet-geosearch-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index ada8ff3d..023b86b8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,12 +20,14 @@ "@phosphor-icons/react": "^2.1.5", "@types/autosuggest-highlight": "^3.2.3", "@types/geojson": "^7946.0.14", + "@types/leaflet-draw": "^1.0.11", "@types/leaflet.markercluster": "^1.5.4", "@types/proj4leaflet": "^1.0.10", "autosuggest-highlight": "^3.3.4", "axios": "^1.7.2", "geojson": "^0.5.0", "leaflet": "^1.9.4", + "leaflet-draw": "^1.0.4", "leaflet-geosearch": "^4.0.0", "leaflet.markercluster": "^1.5.3", "proj4leaflet": "^1.0.2", diff --git a/frontend/src/components/DataView/DataPanel.tsx b/frontend/src/components/DataView/DataPanel.tsx index a6fd10ae..560037fc 100644 --- a/frontend/src/components/DataView/DataPanel.tsx +++ b/frontend/src/components/DataView/DataPanel.tsx @@ -5,43 +5,15 @@ import { CaretDown, MapTrifold } from "@phosphor-icons/react"; import "./DataPanel.css"; import { Tooltip } from "@mui/material"; import { GridToolbar, GridToolbarProps } from "@mui/x-data-grid"; -import { useState } from "react"; - -// Returns a button if the "button" value is set to 1 -const renderDetailsButton = (params: GridRenderCellParams) => { - const value = params.value; - if (value === 1) { - return ( - - - - - - - - ); - } else { - return null; - } -}; - -// Defines the columns of the Datagrid -const columns: GridColDef[] = [ - { - field: "button", - headerName: "button", - width: 60, - renderCell: renderDetailsButton, - }, - { field: "key", headerName: "key", width: 250 }, - { - field: "value", - headerName: "value", - type: "number", - width: 250, - getApplyQuickFilterFn: undefined, - }, -]; +import { useContext, useState } from "react"; +import { DatasetItem } from "../../types/LocationDataTypes"; +import { TabsContext } from "../../contexts/TabsContext"; +import { fetchDatasets } from "../../services/datasetsService"; +import { AlertContext } from "../../contexts/AlertContext"; +import { Dataset } from "../../types/DatasetTypes"; +import L from "leaflet"; +import CustomSvgIcon from "../DatasetsList/CustomSvgIcon"; +import { svgIconDefault } from "../DatasetsList/DatasetsList"; function MyCustomToolbar(props: GridToolbarProps) { return ; @@ -50,9 +22,8 @@ function MyCustomToolbar(props: GridToolbarProps) { interface DataPanelProps { listTitle: string; filterValue: string; - mapRows: object[]; - genericRows: object[]; - extraRows: object[]; + mapRows: DatasetItem[]; + genericRows: DatasetItem[]; } /* @@ -64,14 +35,94 @@ const DataPanel: React.FC = ({ filterValue, mapRows, genericRows, - extraRows, }) => { // Keep track of if tabs are hidden const [ifMapDataTabHidden, toggleMapDataHidden] = useState(false); const [ifGeneralDataTabHidden, toggleGeneralDataHidden] = useState(false); - const [ifExtraDataTabHidden, toggleExtraDataHidden] = - useState(false); + useState(false); + const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); + + const { openNewTab } = useContext(TabsContext); + + const openDatasetFromMapIcon = async (mapId: string) => { + const datasetsData = await fetchDatasets(); + if (datasetsData) { + const datasetToOpen = datasetsData.find( + (dataset) => dataset.datasetId === mapId + ); + if (datasetToOpen) { + const datasetToOpenTransformed: Dataset = { + id: datasetToOpen.datasetId, + displayName: datasetToOpen.name, + shortDescription: datasetToOpen.shortDescription, + datasetIcon: datasetToOpen.icon ? ( + + ) : ( + + ), + metaData: undefined, + data: { + type: "FeatureCollection", + features: [], + }, + lastDataRequestBounds: L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0)), + }; + + openNewTab(datasetToOpenTransformed); + } else { + // Display alert + setCurrentAlertCache({ + ...currentAlertCache, + isAlertOpened: true, + text: "Dataset with provided ID does not exist.", + }); + console.error("Dataset with provided ID does not exist."); + } + } + }; + + // Returns a button if the "button" value is set to 1 + const renderDetailsButton = (params: GridRenderCellParams) => { + const dataObject = params.row as DatasetItem; + if (dataObject.mapId !== "") { + return ( + + + { + openDatasetFromMapIcon(dataObject.mapId); + }} + > + + + + + ); + } else { + return null; + } + }; + + // Defines the columns of the Datagrid + const columns: GridColDef[] = [ + { + field: "button", + headerName: "button", + width: 60, + renderCell: renderDetailsButton, + }, + { field: "key", headerName: "key", width: 250 }, + { + field: "value", + headerName: "value", + type: "number", + width: 250, + getApplyQuickFilterFn: undefined, + }, + ]; return (
@@ -99,6 +150,9 @@ const DataPanel: React.FC = ({ }`} > { + return row.key + row.value; + }} hideFooter={true} disableColumnMenu columnHeaderHeight={0} @@ -149,7 +203,7 @@ const DataPanel: React.FC = ({ ifGeneralDataTabHidden ? "data-panel-toggle-icon-hidden" : "" }`} /> - General Data + Individual Data
= ({ }`} > { + return row.key + row.value; }} - filterModel={{ - items: [ - { field: "key", operator: "contains", value: filterValue }, - ], - }} - density="compact" - disableRowSelectionOnClick - autoHeight - /> - - -
-
{ - toggleExtraDataHidden(!ifExtraDataTabHidden); - }} - > - - Extra Capabilities -
- - polygon[0]); +} function DataView() { - const { currentTabsCache } = useContext(TabsContext); + const [isLoading, setIsLoading] = useState(false); + const { currentTabsCache, getCurrentTab } = useContext(TabsContext); const { currentMapCache, setCurrentMapCache } = useContext(MapContext); const [filterValue, setFilterValue] = useState(""); const [ifNeedsReloading, setIfNeedsReloading] = useState(false); @@ -20,38 +33,75 @@ function DataView() { setFilterValue(event.target.value); }; - const getCurrentTabTitle = (): string => { - const currentTabID = currentTabsCache.currentTabID.toString(); - const currentTab = currentTabsCache.openedTabs.find( - (tab) => tab.id === currentTabID - ); - - return currentTab ? currentTab.dataset.displayName : "No map loaded"; - }; - + /** + * Check if the "Reload data" button should be visible. + * Should be run on selecting different coordinates or changing the current map ID. + */ useEffect(() => { + // Check if different coordinates were selected if ( !ifNeedsReloading && currentMapCache.selectedCoordinates !== null && currentMapCache.loadedCoordinates !== currentMapCache.selectedCoordinates + ) { + setIfNeedsReloading(true); + // Check if tab was switched + } else if ( + !ifNeedsReloading && + currentMapCache.selectedCoordinates !== null && + currentMapCache.currentTabID !== currentTabsCache.currentTabID ) { setIfNeedsReloading(true); } - }, [currentMapCache, ifNeedsReloading]); + }, [currentMapCache, ifNeedsReloading, currentTabsCache.currentTabID]); /** * Reloads the location data. */ const reloadData = async () => { + setIsLoading(true); // Set loading to true before starting the fetch request setIfNeedsReloading(false); + const currentID = currentTabsCache.currentTabID; + const currentCoords = currentMapCache.selectedCoordinates; + // Set the current map cache setCurrentMapCache({ ...currentMapCache, - loadedCoordinates: currentMapCache.selectedCoordinates, + loadedCoordinates: currentCoords, + currentTabID: currentID, }); - const responseData = await fetchLocationData(); - if (responseData) { - setData(responseData); + // Prepare the location data + if (currentCoords) { + let coords: Position[][] = []; + + if (currentCoords instanceof MarkerSelection) { + const singlePosition: Position = [ + currentCoords.marker.lng, + currentCoords.marker.lat, + ]; + coords = [[singlePosition]]; + } else if (currentCoords instanceof PolygonSelection) { + // we have multipolygons which can have quite complicated inner structures. + // we simplfiy fot the current api in a way that we ignore all inner "holes" or other parts and only take + // the outer parts. so the independent general polygons. + coords = getOuterPolygons(currentCoords.polygon); + } + + // Send the location request + const currentTab = getCurrentTab(); + if (currentTab) { + const responseData = await fetchLocationData( + currentTab.dataset.id, + coords + ); + if (responseData) { + setData(responseData); + } + } + } else { + console.log("Currently selected coordinates are null."); } + + setIsLoading(false); // Set loading to false after the fetch request completes }; return ( @@ -61,8 +111,29 @@ function DataView() {
- {currentMapCache.loadedCoordinates.lat.toFixed(6)},{" "} - {currentMapCache.loadedCoordinates.lng.toFixed(6)} +
+ + + {currentMapCache.loadedCoordinates.displayName.substring( + 0, + 40 + ) + + (currentMapCache.loadedCoordinates.displayName.length > 40 + ? "... " + : "")} + + + {currentMapCache.loadedCoordinates instanceof + MarkerSelection && ( +
+ ({currentMapCache.loadedCoordinates.marker.lat.toFixed(6)},{" "} + {currentMapCache.loadedCoordinates.marker.lng.toFixed(6)}) +
+ )} +
- + {isLoading ? ( +
+ +
+ ) : ( + + )} {ifNeedsReloading ? (
diff --git a/frontend/src/components/DatasetsList/DatasetsList.tsx b/frontend/src/components/DatasetsList/DatasetsList.tsx index 67ec06ef..c865c35b 100644 --- a/frontend/src/components/DatasetsList/DatasetsList.tsx +++ b/frontend/src/components/DatasetsList/DatasetsList.tsx @@ -7,22 +7,14 @@ import { ListItemText, } from "@mui/material"; import { useContext, useEffect, useState } from "react"; -import { TabProps, TabsContext } from "../../contexts/TabsContext"; +import { TabsContext } from "../../contexts/TabsContext"; import "./DatasetsList.css"; -import { AlertContext } from "../../contexts/AlertContext"; -import { FeatureCollection } from "geojson"; import L from "leaflet"; import { Dataset, DatasetBasicData } from "../../types/DatasetTypes"; import CustomSvgIcon from "./CustomSvgIcon"; import { fetchDatasets } from "../../services/datasetsService"; -// Define an empty FeatureCollection -const emptyFeatureCollection: FeatureCollection = { - type: "FeatureCollection", - features: [], -}; - -const svgIconDefault: string = +export const svgIconDefault: string = ''; interface DatasetsListProps { @@ -31,8 +23,7 @@ interface DatasetsListProps { const DatasetsList: React.FC = ({ closeDialog }) => { const [datasets, setDatasets] = useState([]); - const { currentTabsCache, setCurrentTabsCache } = useContext(TabsContext); - const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); + const { openNewTab } = useContext(TabsContext); useEffect(() => { const fetchDatasetsData = async () => { @@ -50,7 +41,10 @@ const DatasetsList: React.FC = ({ closeDialog }) => { ), metaData: undefined, - data: emptyFeatureCollection, + data: { + type: "FeatureCollection", + features: [], + }, lastDataRequestBounds: L.latLngBounds( L.latLng(0, 0), L.latLng(0, 0) @@ -67,29 +61,12 @@ const DatasetsList: React.FC = ({ closeDialog }) => { }, []); // Opens a new tab - const openNewTab = (dataset: Dataset) => { - if ( - currentTabsCache.openedTabs.some((tab) => tab.dataset.id === dataset.id) - ) { - setCurrentAlertCache({ - ...currentAlertCache, - isAlertOpened: true, - text: "This dataset was already added.", - }); - return; - } - const newTabID = currentTabsCache.openedTabs.length + 1; - const newTab: TabProps = { - id: newTabID.toString(), - dataset: dataset, - ifPinned: false, - }; - setCurrentTabsCache({ - ...currentTabsCache, - openedTabs: [...currentTabsCache.openedTabs, newTab], - }); + const openNewDataset = (dataset: Dataset) => { + const ifOpened = openNewTab(dataset); // Close the dialog if necessary - closeDialog(); + if (ifOpened) { + closeDialog(); + } }; return ( @@ -102,7 +79,7 @@ const DatasetsList: React.FC = ({ closeDialog }) => { { - openNewTab(dataset); + openNewDataset(dataset); }} > {dataset.datasetIcon} diff --git a/frontend/src/components/MapView/BackgroundMaps/AerialMap.tsx b/frontend/src/components/MapView/BackgroundMaps/AerialMap.tsx new file mode 100644 index 00000000..82b381dd --- /dev/null +++ b/frontend/src/components/MapView/BackgroundMaps/AerialMap.tsx @@ -0,0 +1,23 @@ +import L from "leaflet"; +import { TileLayer, WMSTileLayer } from "react-leaflet"; + +const AerialMap = () => { + return ( +
+ + +
+ ); +}; + +export default AerialMap; diff --git a/frontend/src/components/MapView/BackgroundMaps/NormalMap.tsx b/frontend/src/components/MapView/BackgroundMaps/NormalMap.tsx new file mode 100644 index 00000000..8a2ae738 --- /dev/null +++ b/frontend/src/components/MapView/BackgroundMaps/NormalMap.tsx @@ -0,0 +1,14 @@ +import { TileLayer } from "react-leaflet"; + +const NormalMap = () => { + return ( +
+ +
+ ); +}; + +export default NormalMap; diff --git a/frontend/src/components/MapView/BackgroundMaps/ParcelMap.tsx b/frontend/src/components/MapView/BackgroundMaps/ParcelMap.tsx new file mode 100644 index 00000000..9ab6bd39 --- /dev/null +++ b/frontend/src/components/MapView/BackgroundMaps/ParcelMap.tsx @@ -0,0 +1,22 @@ +import { TileLayer, WMSTileLayer } from "react-leaflet"; + +const ParcelMap = () => { + return ( +
+ + + +
+ ); +}; + +export default ParcelMap; diff --git a/frontend/src/components/MapView/BackgroundMaps/SatelliteMap.tsx b/frontend/src/components/MapView/BackgroundMaps/SatelliteMap.tsx new file mode 100644 index 00000000..118cc600 --- /dev/null +++ b/frontend/src/components/MapView/BackgroundMaps/SatelliteMap.tsx @@ -0,0 +1,23 @@ +import L from "leaflet"; +import { TileLayer, WMSTileLayer } from "react-leaflet"; + +const SatelliteMap = () => { + return ( +
+ + +
+ ); +}; + +export default SatelliteMap; diff --git a/frontend/src/components/MapView/GeoDataFetcher.tsx b/frontend/src/components/MapView/GeoDataFetcher.tsx index fd6c6825..9fc37136 100644 --- a/frontend/src/components/MapView/GeoDataFetcher.tsx +++ b/frontend/src/components/MapView/GeoDataFetcher.tsx @@ -67,6 +67,7 @@ const GeoDataFetcher = ( isAlertOpened: true, text: "Fetching data failed.", }); + console.error("Error fetching data."); } }; @@ -78,6 +79,13 @@ const GeoDataFetcher = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [bounds, zoom, id]); + /** + * Fetches the data at first load. + */ + useEffect(() => { + fetchMetadataAndData(); + }); + return data; }; diff --git a/frontend/src/components/MapView/LeafletMap.css b/frontend/src/components/MapView/LeafletMap.css new file mode 100644 index 00000000..e337a3fe --- /dev/null +++ b/frontend/src/components/MapView/LeafletMap.css @@ -0,0 +1,18 @@ +.leaflet-draw-tooltip { + display: none; +} + +.leaflet-editing-icon { + background-color: #ff0000; + border-color: black; + margin-top: -3px !important; + margin-left: -3px !important; + border-radius: 1rem; + width: 8px !important; + height: 8px !important; +} + +.leaflet-draw-guide-dash { + background-color: #ff0000 !important; + border-radius: 1rem; +} diff --git a/frontend/src/components/MapView/LeafletMap.tsx b/frontend/src/components/MapView/LeafletMap.tsx new file mode 100644 index 00000000..a41dd0a2 --- /dev/null +++ b/frontend/src/components/MapView/LeafletMap.tsx @@ -0,0 +1,236 @@ +import React, { + Fragment, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { MapContainer, ZoomControl } from "react-leaflet"; +import { TabProps, TabsContext } from "../../contexts/TabsContext"; +import { MapContext } from "../../contexts/MapContext"; +import MapDatasetVisualizer from "./MapDatasetVisualizer"; +import { Dataset } from "../../types/DatasetTypes"; +import MapEventsHandler from "./MapEventsHandler"; +import ZoomWarningLabel from "../ZoomWarningLabel/ZoomWarningLabel"; +import L, { LeafletEvent } from "leaflet"; +import "leaflet-draw/dist/leaflet.draw.css"; +import "leaflet-draw"; +import SatelliteMap from "./BackgroundMaps/SatelliteMap"; +import AerialMap from "./BackgroundMaps/AerialMap"; +import NormalMap from "./BackgroundMaps/NormalMap"; +import ParcelMap from "./BackgroundMaps/ParcelMap"; +import "./LeafletMap.css"; +import { + MarkerSelection, + PolygonSelection, +} from "../../types/MapSelectionTypes"; +import { + Feature, + GeoJsonProperties, + Geometry, + MultiPolygon, + Position, +} from "geojson"; + +interface LeafletMapProps { + datasetId: string; + mapType: string; +} + +const LeafletMap: React.FC = ({ datasetId, mapType }) => { + const { currentTabsCache, getCurrentTab, getOrFetchMetadata } = + useContext(TabsContext); + const [map, setMap] = useState(null); + const { currentMapCache, setCurrentMapCache } = useContext(MapContext); + const currentMapCacheRef = useRef(currentMapCache); + const [isGrayscale, setIsGrayscale] = useState(false); + const [polygonDrawer, setPolygonDrawer] = useState( + null + ); + const currentTab = getCurrentTab(); + + // Update ref value whenever currentMapCache changes + useEffect(() => { + currentMapCacheRef.current = currentMapCache; + }, [currentMapCache]); + + /** + * Toggle polygon drawer + */ + useEffect(() => { + if (polygonDrawer) { + if (currentMapCache.isDrawing) { + if (currentMapCache.drawnItems) { + currentMapCache.drawnItems.clearLayers(); + } + setCurrentMapCache({ ...currentMapCache, selectedCoordinates: null }); + polygonDrawer.enable(); + } else { + polygonDrawer.disable(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentMapCache.isDrawing]); + + /** + * Delete the selection if other coordinate was selected + */ + useEffect(() => { + if (currentMapCache.selectedCoordinates instanceof MarkerSelection) { + polygonDrawer?.disable(); + if (currentMapCache.drawnItems) { + currentMapCache.drawnItems.clearLayers(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentMapCache.selectedCoordinates]); + + /** + * Fetches the metadata of the current tab + */ + useEffect(() => { + if (currentTab) { + getOrFetchMetadata(currentTab.dataset.id); + } + }, [currentTab, getOrFetchMetadata]); + + /** + * Refresh the map bounds on map change + */ + useEffect(() => { + if (map) { + const initialBounds = map.getBounds(); + const initialCenter = map.getCenter(); + const initialZoom = map.getZoom(); + const drawnItems = new L.FeatureGroup(); + + setCurrentMapCache((prevCache) => ({ + ...prevCache, + mapInstance: map, + mapCenter: initialCenter, + mapBounds: initialBounds, + zoom: initialZoom, + drawnItems: drawnItems, + })); + // Allow for drawing polygons + map.addLayer(drawnItems); + // Define the options for the polygon drawer + const polygonOptions = { + shapeOptions: { + color: "#ff0000", + weight: 3, + fillOpacity: 0.06, + }, + }; + setPolygonDrawer(new L.Draw.Polygon(map as L.DrawMap, polygonOptions)); + // Bind for polygon created + map.on(L.Draw.Event.CREATED, (event: LeafletEvent) => { + const drawnObject = (event as L.DrawEvents.Created).layer; + if (drawnObject instanceof L.Polygon) { + if (drawnItems) { + drawnItems.addLayer(drawnObject); + const geoJsonObject = drawnObject.toGeoJSON() as Feature< + Geometry, + GeoJsonProperties + >; + let multiPolygon: MultiPolygon; + + // we will probably always encounter only polygons but in a istant future it may be interesting to have multi polygon selection + if (geoJsonObject.geometry.type === "Polygon") { + const polygon = geoJsonObject.geometry + .coordinates as Position[][]; + multiPolygon = { + type: "MultiPolygon", + coordinates: [polygon], + }; + } else if (geoJsonObject.geometry.type === "MultiPolygon") { + multiPolygon = geoJsonObject.geometry as MultiPolygon; + } else { + throw new Error("Unsupported geometry type"); + } + const polygonSelection = new PolygonSelection( + multiPolygon, + "Custom Polygon", + true + ); + + setCurrentMapCache({ + ...currentMapCacheRef.current, + selectedCoordinates: polygonSelection, + isDrawing: false, + }); + } + } + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [map]); + + /** + * Check for the zoom level threshold to apply the zoom warning and its effects + */ + useEffect(() => { + if (currentTab && currentTab.dataset.metaData) { + setIsGrayscale( + currentMapCache.zoom <= currentTab.dataset.metaData.minZoomLevel + ); + } + }, [currentMapCache.zoom, currentTab]); + + /** + * Adds or removes the grayscale css value + */ + useEffect(() => { + if (map) { + if (isGrayscale) { + map.getContainer().classList.add("grayscale-overlay"); + } else { + map.getContainer().classList.remove("grayscale-overlay"); + } + } + }, [isGrayscale, map]); + + const pinnedFeatureCollections = currentTabsCache.openedTabs + .filter((tab: TabProps) => tab.ifPinned) + .map((tab: TabProps) => tab.dataset); + + const isCurrentDataPinned = pinnedFeatureCollections.some( + (dataset: Dataset) => dataset.id === datasetId + ); + + return ( +
+ + + {isGrayscale ? ( + + ) : ( +
+ {pinnedFeatureCollections.map((dataset: Dataset, index: number) => ( + + ))} + {!isCurrentDataPinned && currentTab && ( + + )} +
+ )} + + {mapType === "satellite" && } + {mapType === "aerial" && } + {mapType === "normal" && } + {mapType === "parcel" && } + +
+
+ ); +}; + +export default LeafletMap; diff --git a/frontend/src/components/MapView/MapEventsHandler.tsx b/frontend/src/components/MapView/MapEventsHandler.tsx index 8048fd17..e59cc526 100644 --- a/frontend/src/components/MapView/MapEventsHandler.tsx +++ b/frontend/src/components/MapView/MapEventsHandler.tsx @@ -1,12 +1,12 @@ -import { Fragment, useContext } from "react"; -import { Marker } from "react-leaflet/Marker"; -import { Popup } from "react-leaflet/Popup"; -import { useMap, useMapEvents } from "react-leaflet/hooks"; +import React, { Fragment, useContext } from "react"; +import { Marker } from "react-leaflet"; +import { useMapEvents } from "react-leaflet/hooks"; import { MapContext } from "../../contexts/MapContext"; import L, { DivIcon } from "leaflet"; import { MapPin } from "@phosphor-icons/react"; import { createRoot } from "react-dom/client"; import { flushSync } from "react-dom"; +import { MarkerSelection } from "../../types/MapSelectionTypes"; // Utility function to render a React component to HTML string const renderToHtml = (Component: React.FC) => { @@ -25,23 +25,23 @@ const divIconMarker: DivIcon = L.divIcon({ iconAnchor: [18, 36], // Adjust the anchor point as needed }); -const MapEventsHandler = () => { +const MapEventsHandler: React.FC = () => { const { currentMapCache, setCurrentMapCache } = useContext(MapContext); - const setPosition = (latlng: L.LatLng) => { - setCurrentMapCache({ ...currentMapCache, selectedCoordinates: latlng }); - }; - - const map = useMap(); // Add events useMapEvents({ click: (event) => { - currentMapCache.polygon?.remove(); - setCurrentMapCache({ - ...currentMapCache, - selectedCoordinates: event.latlng, - polygon: null, - }); + if (!currentMapCache.isDrawing) { + const markerSelection = new MarkerSelection( + event.latlng, + "Custom Marker", + true + ); + setCurrentMapCache({ + ...currentMapCache, + selectedCoordinates: markerSelection, + }); + } }, moveend: (event) => { setCurrentMapCache({ @@ -53,34 +53,11 @@ const MapEventsHandler = () => { }, }); - return currentMapCache.selectedCoordinates !== null ? ( - - - { - map - .locate({ setView: true }) - .on("locationfound", function (event) { - //currentMapCache.polygon?.remove(); - setPosition(event.latlng); - map.flyTo(event.latlng, map.getZoom(), { - animate: true, - duration: 50, - }); - }) - // If access to the location was denied - .on("locationerror", function (event) { - console.log(event); - alert("Location access denied."); - }); - }} - > - {currentMapCache.selectedCoordinates.lat.toFixed(4)},{" "} - {currentMapCache.selectedCoordinates.lng.toFixed(4)} - - - + return currentMapCache.selectedCoordinates instanceof MarkerSelection ? ( + ) : ( ); diff --git a/frontend/src/components/MapView/MapOptions.css b/frontend/src/components/MapView/MapOptions.css index d2b02e6e..620e3629 100644 --- a/frontend/src/components/MapView/MapOptions.css +++ b/frontend/src/components/MapView/MapOptions.css @@ -47,7 +47,7 @@ color: #535bf2; } -.layers-map-icon { +.options-icons { width: 1.5rem; height: 1.5rem; } @@ -65,5 +65,26 @@ } .image-hover-effect:hover { - border-color: blue; -} \ No newline at end of file + border-color: blue; +} + +.draw-polygon-icon-container { + width: 2.1rem; + height: 2.1rem; + position: absolute; + top: 7.5rem; + right: 0.6rem; + cursor: pointer; + z-index: 2; + display: flex; + flex-direction: column; + background-color: white; + justify-content: center; + align-items: center; + border: 2px solid rgba(0, 0, 0, 0.27); + box-shadow: none; +} +.draw-polygon-icon-container:hover { + background-color: #f4f4f4; + color: #535bf2; +} diff --git a/frontend/src/components/MapView/MapOptions.tsx b/frontend/src/components/MapView/MapOptions.tsx index b84d5313..8db0839a 100644 --- a/frontend/src/components/MapView/MapOptions.tsx +++ b/frontend/src/components/MapView/MapOptions.tsx @@ -1,17 +1,17 @@ -import React, { useState } from "react"; -import { Paper, Popover, Grid, Typography, Box } from "@mui/material"; +import React, { useContext, useState } from "react"; +import { Paper, Popover, Grid, Typography, Box, Tooltip } from "@mui/material"; import "./MapOptions.css"; -import { StackSimple } from "@phosphor-icons/react"; +import { Polygon, StackSimple } from "@phosphor-icons/react"; import SearchBar from "../SearchBar/SearchBar"; +import { MapContext } from "../../contexts/MapContext"; interface MapOptionsProps { - onMapTypeChange: ( - type: "normal" | "satellite" | "parzellar" | "aerial" - ) => void; + onMapTypeChange: (type: "normal" | "satellite" | "parcel" | "aerial") => void; } const MapOptions: React.FC = ({ onMapTypeChange }) => { const [anchorEl, setAnchorEl] = useState(null); + const { currentMapCache, setCurrentMapCache } = useContext(MapContext); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -22,7 +22,7 @@ const MapOptions: React.FC = ({ onMapTypeChange }) => { }; const handleMapTypeChange = ( - type: "normal" | "satellite" | "parzellar" | "aerial" + type: "normal" | "satellite" | "parcel" | "aerial" ) => { onMapTypeChange(type); handleClose(); @@ -36,12 +36,27 @@ const MapOptions: React.FC = ({ onMapTypeChange }) => {
-
- -
+ +
+ +
+
+ +
{ + setCurrentMapCache({ + ...currentMapCache, + isDrawing: !currentMapCache.isDrawing, + }); + }} + className="draw-polygon-icon-container leaflet-touch leaflet-bar leaflet-control leaflet-control-custom" + > + +
+
= ({ onMapTypeChange }) => { handleClose(); }} /> - Aerial - {/* - - Satellite { - handleMapTypeChange("parzellar"); - handleClose(); - }} - /> - - Parzellar - - - */} diff --git a/frontend/src/components/MapView/MapView.css b/frontend/src/components/MapView/MapView.css index 32b25569..731cd6c1 100644 --- a/frontend/src/components/MapView/MapView.css +++ b/frontend/src/components/MapView/MapView.css @@ -10,5 +10,5 @@ } .grayscale-overlay { - filter: grayscale(80%); + filter: grayscale(40%); } diff --git a/frontend/src/components/MapView/MapView.tsx b/frontend/src/components/MapView/MapView.tsx index ba0c7569..5f0e1981 100644 --- a/frontend/src/components/MapView/MapView.tsx +++ b/frontend/src/components/MapView/MapView.tsx @@ -1,10 +1,4 @@ -import { Fragment, useContext, useEffect, useState } from "react"; -import { - MapContainer, - TileLayer, - WMSTileLayer, - ZoomControl, -} from "react-leaflet"; +import { useState } from "react"; import "leaflet/dist/leaflet.css"; import "leaflet.markercluster/dist/MarkerCluster.css"; import "leaflet.markercluster/dist/MarkerCluster.Default.css"; @@ -14,12 +8,7 @@ import L from "leaflet"; import icon from "leaflet/dist/images/marker-icon.png"; import iconShadow from "leaflet/dist/images/marker-shadow.png"; import MapOptions from "./MapOptions"; -import { MapContext } from "../../contexts/MapContext"; -import { TabProps, TabsContext } from "../../contexts/TabsContext"; -import MapDatasetVisualizer from "./MapDatasetVisualizer"; -import MapEventsHandler from "./MapEventsHandler"; -import { Dataset } from "../../types/DatasetTypes"; -import ZoomWarningLabel from "../ZoomWarningLabel/ZoomWarningLabel"; +import LeafletMap from "./LeafletMap"; const DefaultIcon = L.icon({ iconUrl: icon, @@ -34,156 +23,24 @@ interface MapViewProps { } const MapView: React.FC = ({ datasetId }) => { - const { currentTabsCache, getCurrentTab, getOrFetchMetadata } = - useContext(TabsContext); - const [map, setMap] = useState(null); - const { currentMapCache, setCurrentMapCache } = useContext(MapContext); - const [isGrayscale, setIsGrayscale] = useState(false); const [mapType, setMapType] = useState< - "normal" | "satellite" | "parzellar" | "aerial" + "normal" | "satellite" | "parcel" | "aerial" >("normal"); + /** + * Changes the layer type + * @param type type of the layer + */ const handleMapTypeChange = ( - type: "normal" | "satellite" | "parzellar" | "aerial" + type: "normal" | "satellite" | "parcel" | "aerial" ) => { setMapType(type); }; - const currentTab = getCurrentTab(); - - useEffect(() => { - if (currentTab) { - getOrFetchMetadata(currentTab.dataset.id); - } - }, [currentTab, getOrFetchMetadata]); - - useEffect(() => { - if (map) { - const initialBounds = map.getBounds(); - const initialCenter = map.getCenter(); - const initialZoom = map.getZoom(); - - setCurrentMapCache((prevCache) => ({ - ...prevCache, - mapInstance: map, - mapCenter: initialCenter, - mapBounds: initialBounds, - zoom: initialZoom, - })); - } - }, [map, setCurrentMapCache]); - - useEffect(() => { - if (currentTab && currentTab.dataset.metaData) { - setIsGrayscale( - currentMapCache.zoom <= currentTab.dataset.metaData.minZoomLevel - ); - } - }, [currentMapCache.zoom, currentTab]); - - useEffect(() => { - if (map) { - if (isGrayscale) { - map.getContainer().classList.add("grayscale-overlay"); - } else { - map.getContainer().classList.remove("grayscale-overlay"); - } - } - }, [isGrayscale, map]); - - const pinnedFeatureCollections = currentTabsCache.openedTabs - .filter((tab: TabProps) => tab.ifPinned) - .map((tab: TabProps) => tab.dataset); - - const isCurrentDataPinned = pinnedFeatureCollections.some( - (dataset: Dataset) => dataset.id === datasetId - ); - return (
+ - - - {isGrayscale ? ( - - ) : ( -
- {pinnedFeatureCollections.map((dataset: Dataset, index: number) => ( - - ))} - {!isCurrentDataPinned && currentTab && ( - - )} -
- )} - - - {mapType === "satellite" && ( -
- - -
- )} - {mapType === "aerial" && ( -
- - -
- )} - {mapType === "normal" && ( -
- -
- )} - {mapType === "parzellar" && ( -
- - - -
- )} - -
); }; diff --git a/frontend/src/components/MultiMap/MultiMap.css b/frontend/src/components/MultiMap/MultiMap.css index 98a11f97..08af6286 100644 --- a/frontend/src/components/MultiMap/MultiMap.css +++ b/frontend/src/components/MultiMap/MultiMap.css @@ -4,8 +4,8 @@ max-width: 100%; display: flex; flex-direction: column; - padding-left:20px; - padding-right:10px; + padding-left: 20px; + padding-right: 10px; padding-top: 3px; } @@ -75,33 +75,6 @@ right: 0.2rem; } -.add-tab-button { - background-color: white; - border-color: gray; - color: gray; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - margin: 0.4rem; - min-width: 2rem; - min-height: 2rem; - width: 2rem; - height: 2rem; - margin-right: 2rem; -} - -.add-tab-button:hover { - background-color: rgb(245, 245, 245); - border-color: black; - color: black; -} - -.add-tab-button:focus { - color: black; - border-color: black; -} - .MuiTabs-scrollButtons.Mui-disabled { opacity: 0.3 !important; } diff --git a/frontend/src/components/MultiMap/NewTabButton.css b/frontend/src/components/MultiMap/NewTabButton.css new file mode 100644 index 00000000..a7b70ade --- /dev/null +++ b/frontend/src/components/MultiMap/NewTabButton.css @@ -0,0 +1,27 @@ +.add-tab-button { + background-color: white; + border-color: gray; + color: gray; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + margin: 0.4rem; + min-width: 2rem; + min-height: 2rem; + width: 2rem; + height: 2rem; + margin-right: 2rem; +} + +.add-tab-button:hover { + background-color: rgb(245, 245, 245); + border-color: black; + color: black; +} + +.add-tab-button:focus { + color: none; + border-color: none; + outline: none; +} diff --git a/frontend/src/components/MultiMap/NewTabButton.tsx b/frontend/src/components/MultiMap/NewTabButton.tsx index 7c4315c7..8e2d2c4e 100644 --- a/frontend/src/components/MultiMap/NewTabButton.tsx +++ b/frontend/src/components/MultiMap/NewTabButton.tsx @@ -2,6 +2,7 @@ import { Tooltip } from "@mui/material"; import { Plus } from "@phosphor-icons/react"; import { useState } from "react"; import DatasetsPopUp from "../PopUp/DatasetsPopUp"; +import "./NewTabButton.css"; const NewTabButton = () => { // Stores the state of if the datasets popup is open diff --git a/frontend/src/components/SearchBar/SearchBar.tsx b/frontend/src/components/SearchBar/SearchBar.tsx index 6dd978a2..40ca0c6a 100644 --- a/frontend/src/components/SearchBar/SearchBar.tsx +++ b/frontend/src/components/SearchBar/SearchBar.tsx @@ -8,9 +8,13 @@ import { MapSelection, SearchContext } from "../../contexts/SearchContext"; import { OpenStreetMapProvider } from "leaflet-geosearch"; import { LatLng } from "leaflet"; import { MapContext } from "../../contexts/MapContext"; -import L from "leaflet"; import "./SearchBar.css"; -import { GeoJSON } from "geojson"; +import { GeoJSON, MultiPolygon } from "geojson"; +import { + MarkerSelection, + PolygonSelection, +} from "../../types/MapSelectionTypes"; +import L from "leaflet"; declare module "leaflet-geosearch/dist/providers/openStreetMapProvider.js" { interface RawResult { @@ -111,24 +115,42 @@ const SearchBar: React.FC = () => { if (mapInstance) { if (item.area && item.bounds) { mapInstance.flyToBounds(item.bounds, { animate: true, duration: 5 }); - const drawPolygon = L.geoJSON(item.polygon, { - style: { - color: "#ff0000", - weight: 2, - fillOpacity: 0, - }, - }); - drawPolygon.addTo(mapInstance); - setCurrentMapCache({ - ...currentMapCache, - polygon: drawPolygon, - selectedCoordinates: null, - }); + if (currentMapCache.drawnItems) { + currentMapCache.drawnItems.clearLayers(); + } + if (item.polygon) { + const drawPolygon = L.geoJSON(item.polygon, { + style: { + color: "#ff0000", + weight: 2, + fillOpacity: 0.06, + }, + }); + drawPolygon.addTo(currentMapCache.drawnItems!); + const polygonSelection = new PolygonSelection( + item.polygon as MultiPolygon, + item.displayName, + false + ); + setCurrentMapCache({ + ...currentMapCache, + selectedCoordinates: polygonSelection, + }); + } } else { - currentMapCache.selectedCoordinates = targetPosition; - mapInstance.flyTo(targetPosition, 13, { animate: true, duration: 5 }); + // Select a marker on the map + const markerSelection = new MarkerSelection( + targetPosition, + item.displayName, + false + ); + currentMapCache.selectedCoordinates = markerSelection; + mapInstance.flyTo(targetPosition, currentMapCache.zoom, { + animate: true, + duration: 5, + }); } - } else console.log("no map instance"); + } else console.log("No map instance"); }; const getUniqueOptions = (options: MapSelection[]) => { @@ -156,7 +178,7 @@ const SearchBar: React.FC = () => { getOptionLabel={(option) => typeof option === "string" ? option : option.displayName } - freeSolo={inputValue?.length ? false : true} + freeSolo={true} loading={loading} forcePopupIcon={false} filterOptions={(x) => x} @@ -183,19 +205,12 @@ const SearchBar: React.FC = () => { } }} onInputChange={(_event, newInputValue) => { - if (newInputValue === "") { - currentMapCache.polygon?.remove(); - setCurrentMapCache({ - ...currentMapCache, - polygon: null, - }); - } setInputValue(newInputValue); }} renderInput={(params) => ( Search…
} + placeholder="Search..." size="small" sx={{ width: inputValue.length > 0 ? "100%" : 150, @@ -237,7 +252,7 @@ const SearchBar: React.FC = () => { ); return ( -
  • +
  • Promise; + openNewTab: (datasetID: Dataset) => boolean; }; // Provider component props type @@ -46,6 +48,7 @@ export const TabsContext = createContext({ setCurrentTabsCache: () => null, getCurrentTab: () => undefined, getOrFetchMetadata: async () => undefined, + openNewTab: () => false, }); // Provider component @@ -54,6 +57,7 @@ export const TabsContextProvider: React.FC = ({ }) => { const [currentTabsCache, setCurrentTabsCache] = useState(defaultTabsCache); + const { currentAlertCache, setCurrentAlertCache } = useContext(AlertContext); /** * Returns the currently opened tab @@ -113,11 +117,43 @@ export const TabsContextProvider: React.FC = ({ } }; + /** + * Opens a new tab + * @param dataset a dataset id to open + */ + const openNewTab = (dataset: Dataset) => { + if ( + currentTabsCache.openedTabs.some((tab) => tab.dataset.id === dataset.id) + ) { + setCurrentAlertCache({ + ...currentAlertCache, + isAlertOpened: true, + text: "This dataset was already added.", + }); + return false; + } + + const newTabID = currentTabsCache.openedTabs.length + 1; + const newTab: TabProps = { + id: newTabID.toString(), + dataset: dataset, + ifPinned: false, + }; + + setCurrentTabsCache({ + ...currentTabsCache, + currentTabID: newTab.id, + openedTabs: [...currentTabsCache.openedTabs, newTab], + }); + return true; + }; + const value = { currentTabsCache, setCurrentTabsCache, getCurrentTab, getOrFetchMetadata, + openNewTab, }; return {children}; diff --git a/frontend/src/services/locationDataService.ts b/frontend/src/services/locationDataService.ts index 86d14449..f4438ac4 100644 --- a/frontend/src/services/locationDataService.ts +++ b/frontend/src/services/locationDataService.ts @@ -1,18 +1,25 @@ import axios from "axios"; import { LocationDataResponse } from "../types/LocationDataTypes"; import { getAPIGatewayURL } from "../utils/apiGatewayURL"; +import { Position } from "geojson"; -export const fetchLocationData = async (): Promise< - LocationDataResponse | undefined -> => { +/** + * Fetches the data from a specific location + * @param datasetId the dataset ID of the current map + * @param location an array of coordinates + * @returns + */ +export const fetchLocationData = async ( + datasetId: string, + location: Position[][] +): Promise => { + // Build the request body const requestBody = { - datasetId: "example_dataset", - location: [ - { latitude: 51.509865, longitude: -0.118092 }, // Example coordinate - ], + datasetId: datasetId, + location: location, }; - try { + console.log(requestBody); const response = await axios.put( getAPIGatewayURL() + "/api/loadLocationData", requestBody diff --git a/frontend/src/types/LocationDataTypes.tsx b/frontend/src/types/LocationDataTypes.tsx index ec9471da..5163856e 100644 --- a/frontend/src/types/LocationDataTypes.tsx +++ b/frontend/src/types/LocationDataTypes.tsx @@ -1,7 +1,6 @@ export interface LocationDataResponse { - currentDatasetData: DatasetItem[]; + individualData: DatasetItem[]; generalData: DatasetItem[]; - extraRows: DatasetItem[]; } export interface DatasetItem { diff --git a/frontend/src/types/MapSelectionTypes.tsx b/frontend/src/types/MapSelectionTypes.tsx new file mode 100644 index 00000000..f1c0f8c3 --- /dev/null +++ b/frontend/src/types/MapSelectionTypes.tsx @@ -0,0 +1,32 @@ +import { LatLng } from "leaflet"; +import { MultiPolygon } from "geojson"; + +// Define PolygonSelection class +export class PolygonSelection { + polygon: MultiPolygon; + displayName: string; + ifHandSelected: boolean; + + constructor( + polygon: MultiPolygon, + displayName: string, + ifHandSelected: boolean + ) { + this.polygon = polygon; + this.displayName = displayName; + this.ifHandSelected = ifHandSelected; + } +} + +// An interface for a single marker selection +export class MarkerSelection { + marker: LatLng; + displayName: string; + ifHandSelected: boolean; + + constructor(marker: LatLng, displayName: string, ifHandSelected: boolean) { + this.marker = marker; + this.displayName = displayName; + this.ifHandSelected = ifHandSelected; + } +}