diff --git a/Controllers/BuildingController.cs b/Controllers/BuildingController.cs index 361e821..1f069b7 100644 --- a/Controllers/BuildingController.cs +++ b/Controllers/BuildingController.cs @@ -3,16 +3,9 @@ using SimpleWebAppReact.Entities; using Microsoft.Extensions.Logging; using MongoDB.Driver; +using Newtonsoft.Json.Linq; using SimpleWebAppReact.Services; - - - - - - - - namespace SimpleWebAppReact.Controllers { @@ -25,11 +18,16 @@ public class BuildingController : ControllerBase { private readonly ILogger _logger; private readonly IMongoCollection? _buildings; + private readonly BuildingOutlineService _buildingOutlineService; + private readonly HttpClient _httpClient; + private readonly string _googleApiKey = "AIzaSyCzKs4kUhXuPhBxYB2BU0ODXXIUBJnenhA"; - public BuildingController(ILogger logger, MongoDbService mongoDbService) + public BuildingController(ILogger logger, MongoDbService mongoDbService, BuildingOutlineService buildingOutlineService, HttpClient httpClient) { _logger = logger; _buildings = mongoDbService.Database?.GetCollection("building"); + _buildingOutlineService = buildingOutlineService; + _httpClient = httpClient; } /// @@ -110,6 +108,37 @@ int FuzzScore(Building building) [HttpPost] public async Task Post(Building building) { + // Prepare the Geocoding API request + string url = $"https://maps.googleapis.com/maps/api/geocode/json?address={building.Address}&key={_googleApiKey}"; + + try + { + // Send the request to Google Maps Geocoding API + var response = await _httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + var jsonResponse = await response.Content.ReadAsStringAsync(); + var jsonData = JObject.Parse(jsonResponse); + + // Parse coordinates from the response + var location = jsonData["results"]?[0]?["geometry"]?["location"]; + var latitude = location?["lat"]?.ToObject(); + var longitude = location?["lng"]?.ToObject(); + + if (latitude == null || longitude == null) + { + return BadRequest(new { message = "Could not get coordinates from the address." }); + } + + // Set the coordinates in the building entity + building.Latitude = latitude.Value; + building.Longitude = longitude.Value; + } + catch (HttpRequestException e) + { + _logger.LogError(e, "Error while calling Google Maps API"); + return StatusCode(500, new { message = "Error while calling Google Maps API." }); + } + await _buildings.InsertOneAsync(building); return CreatedAtAction(nameof(GetById), new { id = building.Id }, building); } @@ -139,5 +168,86 @@ public async Task Delete(string id) await _buildings.DeleteOneAsync(filter); return Ok(); } + + /// + /// Gets a building's outline, made of coordinates + /// + /// + /// + /// + [HttpGet("outline")] + public async Task GetBuildingOutlineByPoint( + [FromQuery] string id, + [FromQuery] double radius = 0.001) // default radius ~100m + { + double targetLatitude, targetLongitude; + + // ID is given + if (!string.IsNullOrEmpty(id)) + { + // Query the database to get the building coordinates using the building's ID + var filter = Builders.Filter.Eq(x => x.Id, id); + var building = _buildings.Find(filter).FirstOrDefault(); + + if (building == null) + { + return NotFound($"Building with ID {id} not found."); + } + + targetLatitude = building.Latitude ?? 0.0; + targetLongitude = building.Longitude ?? 0.0; + } + else + { + // Return error if id not provided + return BadRequest("Either id must be provided."); + } + + // Retrieve building outlines within the bounding box + var outlineTuples = await _buildingOutlineService.GetBuildingOutline(targetLatitude, targetLongitude, radius); + + // convert the returned variable into a object that can be serialized + var buildingOutlines = outlineTuples.Select((outline, index) => new BuildingOutline + { + BuildingID = id ?? $"building_{index + 1}", + Coordinates = outline.Select(coordinate => new Coordinate + { + Latitude = coordinate.Lat, + Longitude = coordinate.Lon + }).ToList() + }).ToList(); + + // Print each building outline's coordinates to the console + // PrintBuildingOutlines(buildingOutlines); + + // Return the building outlines as JSON + return Ok(buildingOutlines); + } + private void PrintBuildingOutlines(List> buildingOutlines) + { + for (int i = 0; i < buildingOutlines.Count; i++) + { + Console.WriteLine($"Building {i + 1} Outline:"); + foreach (var (Lat, Lon) in buildingOutlines[i]) + { + Console.WriteLine($" Latitude: {Lat}, Longitude: {Lon}"); + } + Console.WriteLine(); // Blank line between buildings + } + } + + // Private classes for buildingOutline + // dotnet's serializer will automatically retain structure of the objects when returning it as JSON + private class Coordinate + { + public double Latitude { get; set; } + public double Longitude { get; set; } + } + private class BuildingOutline + { + public string BuildingID { get; set; } = string.Empty; + public List Coordinates { get; set; } = new List(); + } } + } \ No newline at end of file diff --git a/Controllers/BuildingOutlineService.cs b/Controllers/BuildingOutlineService.cs new file mode 100644 index 0000000..1618aa4 --- /dev/null +++ b/Controllers/BuildingOutlineService.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; + +public class BuildingOutlineService +{ + private readonly HttpClient _httpClient; + + public BuildingOutlineService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + // Method to get building outlines with multiple coordinates for diverse polygon shapes + public async Task>> GetBuildingOutline(double latitude, double longitude, double radius) + { + double minLat = latitude - radius; + double minLon = longitude - radius; + double maxLat = latitude + radius; + double maxLon = longitude + radius; + + // Overpass API endpoint + string url = "http://overpass-api.de/api/interpreter"; + + // Create Overpass QL query for buildings within the bounding box + string query = $@" + [out:json]; + ( + way[""building""]({minLat}, {minLon}, {maxLat}, {maxLon}); + ); + out geom; + "; + + // Send the query as a POST request + var response = await _httpClient.PostAsync(url, new StringContent(query)); + response.EnsureSuccessStatusCode(); + var json = await response.Content.ReadAsStringAsync(); + + // Parse JSON response to extract coordinates for polygons + var buildingPolygons = ParseBuildingCoordinates(json); + + return buildingPolygons; + } + + // Helper method to parse JSON and generate list of coordinates for diverse polygon shapes + private List> ParseBuildingCoordinates(string json) + { + var data = JsonDocument.Parse(json); + var buildings = new List>(); + + // Loop through each element to find building polygons with diverse shapes + foreach (var element in data.RootElement.GetProperty("elements").EnumerateArray()) + { + if (element.GetProperty("type").GetString() == "way" && element.TryGetProperty("geometry", out var geometry)) + { + var coordinates = new List<(double Lat, double Lon)>(); + + // Collect each point's latitude and longitude for this building polygon + foreach (var point in geometry.EnumerateArray()) + { + double lat = point.GetProperty("lat").GetDouble(); + double lon = point.GetProperty("lon").GetDouble(); + coordinates.Add((lat, lon)); + } + + // Ensure the polygon is closed by adding the first coordinate at the end if necessary + if (coordinates.Count > 0 && coordinates[0] != coordinates[^1]) + { + coordinates.Add(coordinates[0]); + } + + buildings.Add(coordinates); + } + } + + return buildings; + } +} diff --git a/Controllers/MapsController.cs b/Controllers/MapsController.cs index 39a5437..89d564f 100644 --- a/Controllers/MapsController.cs +++ b/Controllers/MapsController.cs @@ -85,5 +85,75 @@ public async Task GetDistance(string buildingId1, string building return StatusCode(500, new { message = "Error while calling Google Maps API." }); } } + [HttpPost("set-coordinates")] + public async Task SetCoordinates(string buildingId) + { + if (_buildings == null) + { + return StatusCode(500, new { message = "Building collection not initialized." }); + } + + // Fetch the building from the database using its ID + var building = await _buildings.Find(b => b.Id == buildingId).FirstOrDefaultAsync(); + + if (building == null) + { + return NotFound(new { message = "Building not found." }); + } + + // Ensure the address is not null or empty + if (string.IsNullOrEmpty(building.Address)) + { + return BadRequest(new { message = "Address is missing for the building." }); + } + + // Prepare the Geocoding API request + string url = $"https://maps.googleapis.com/maps/api/geocode/json?address={building.Address}&key={_googleApiKey}"; + + try + { + // Send the request to Google Maps Geocoding API + var response = await _httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + var jsonResponse = await response.Content.ReadAsStringAsync(); + var jsonData = JObject.Parse(jsonResponse); + + // Parse coordinates from the response + var location = jsonData["results"]?[0]?["geometry"]?["location"]; + var latitude = location?["lat"]?.ToObject(); + var longitude = location?["lng"]?.ToObject(); + + if (latitude == null || longitude == null) + { + return BadRequest(new { message = "Could not get coordinates from the address." }); + } + + // Set the coordinates in the building entity + building.Latitude = latitude.Value; + building.Longitude = longitude.Value; + + // Update the building in the database + var updateResult = await _buildings.ReplaceOneAsync(b => b.Id == buildingId, building); + + if (!updateResult.IsAcknowledged) + { + return StatusCode(500, new { message = "Failed to update building coordinates in the database." }); + } + + return Ok(new + { + message = "Coordinates updated successfully.", + buildingId, + latitude, + longitude + }); + } + catch (HttpRequestException e) + { + _logger.LogError(e, "Error while calling Google Maps API"); + return StatusCode(500, new { message = "Error while calling Google Maps API." }); + } + } + } diff --git a/Entities/Building.cs b/Entities/Building.cs index 4d74096..e2ac642 100644 --- a/Entities/Building.cs +++ b/Entities/Building.cs @@ -19,14 +19,10 @@ public class Building [BsonElement("address"), BsonRepresentation(BsonType.String)] public string? Address { get; set; } -// : { type: , coordinates: } - - - -// location: { -// type: "Point", -// coordinates: [-73.856077, 40.848447] -// } -// : [, ] + [BsonElement("latitude"), BsonRepresentation(BsonType.Double)] + public double? Latitude { get; set; } + + [BsonElement("longitude"), BsonRepresentation(BsonType.Double)] + public double? Longitude { get; set; } } diff --git a/Program.cs b/Program.cs index 4b111f2..5a81808 100644 --- a/Program.cs +++ b/Program.cs @@ -11,6 +11,7 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddSingleton(); +builder.Services.AddHttpClient(); var app = builder.Build(); diff --git a/Startup.cs b/Startup.cs index 2c32a2e..f274bda 100644 --- a/Startup.cs +++ b/Startup.cs @@ -18,6 +18,8 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); services.AddHttpClient(); + services.AddSingleton(); + // Configure CORS to allow requests from React Native frontend services.AddCors(options => {