Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Polygon tracing #42

Merged
merged 13 commits into from
Nov 13, 2024
78 changes: 69 additions & 9 deletions Controllers/BuildingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,9 @@
using SimpleWebAppReact.Entities;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using SimpleWebAppReact.Services;









namespace SimpleWebAppReact.Controllers
{

Expand All @@ -25,11 +18,15 @@
{
private readonly ILogger<BuildingController> _logger;
private readonly IMongoCollection<Building>? _buildings;
private readonly BuildingOutlineService _buildingOutlineService;
private readonly HttpClient _httpClient;
private readonly string _googleApiKey = "AIzaSyCzKs4kUhXuPhBxYB2BU0ODXXIUBJnenhA";

public BuildingController(ILogger<BuildingController> logger, MongoDbService mongoDbService)
public BuildingController(ILogger<BuildingController> logger, MongoDbService mongoDbService, BuildingOutlineService buildingOutlineService)

Check warning on line 25 in Controllers/BuildingController.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable field '_httpClient' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
{
_logger = logger;
_buildings = mongoDbService.Database?.GetCollection<Building>("building");
_buildingOutlineService = buildingOutlineService;
}

/// <summary>
Expand Down Expand Up @@ -89,7 +86,7 @@
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
public async Task<ActionResult<Building?>> GetById(string id)

Check warning on line 89 in Controllers/BuildingController.cs

View workflow job for this annotation

GitHub Actions / test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
// Simple validation to check if the ID is not null
if (string.IsNullOrEmpty(id))
Expand All @@ -110,7 +107,38 @@
[HttpPost]
public async Task<ActionResult> 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<double>();
var longitude = location?["lng"]?.ToObject<double>();

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);

Check warning on line 141 in Controllers/BuildingController.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
return CreatedAtAction(nameof(GetById), new { id = building.Id }, building);
}

Expand All @@ -123,7 +151,7 @@
public async Task<ActionResult> Update(Building building)
{
var filter = Builders<Building>.Filter.Eq(x => x.Id, building.Id);
await _buildings.ReplaceOneAsync(filter, building);

Check warning on line 154 in Controllers/BuildingController.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
return Ok();
}

Expand All @@ -139,5 +167,37 @@
await _buildings.DeleteOneAsync(filter);
return Ok();
}

[HttpGet("outline")]
public async Task<ActionResult> GetBuildingOutlineByPoint(
[FromQuery] double latitude,
[FromQuery] double longitude,
[FromQuery] double radius = 0.001) // default radius ~100m
{
// Define a bounding box around the point


// Retrieve building outlines within the bounding box
var buildingOutlines = await _buildingOutlineService.GetBuildingOutline(latitude, longitude, radius);

// Print each building outline's coordinates to the console
PrintBuildingOutlines(buildingOutlines);

// Return the building outlines as JSON
return Ok(buildingOutlines);
}
private void PrintBuildingOutlines(List<List<(double Lat, double Lon)>> 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
}
}
}

}
80 changes: 80 additions & 0 deletions Controllers/BuildingOutlineService.cs
Original file line number Diff line number Diff line change
@@ -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<List<List<(double Lat, double Lon)>>> 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<List<(double Lat, double Lon)>> ParseBuildingCoordinates(string json)
{
var data = JsonDocument.Parse(json);
var buildings = new List<List<(double Lat, double Lon)>>();

// 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;
}
}
70 changes: 70 additions & 0 deletions Controllers/MapsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,75 @@ public async Task<IActionResult> GetDistance(string buildingId1, string building
return StatusCode(500, new { message = "Error while calling Google Maps API." });
}
}
[HttpPost("set-coordinates")]
public async Task<IActionResult> 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<double>();
var longitude = location?["lng"]?.ToObject<double>();

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." });
}
}

}

14 changes: 5 additions & 9 deletions Entities/Building.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@ public class Building
[BsonElement("address"), BsonRepresentation(BsonType.String)]
public string? Address { get; set; }

// <field>: { type: <GeoJSON type> , coordinates: <coordinates> }



// location: {
// type: "Point",
// coordinates: [-73.856077, 40.848447]
// }
// <field>: [<longitude>, <latitude> ]
[BsonElement("latitude"), BsonRepresentation(BsonType.Double)]
public double? Latitude { get; set; }

[BsonElement("longitude"), BsonRepresentation(BsonType.Double)]
public double? Longitude { get; set; }

}
1 change: 1 addition & 0 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<MongoDbService>();
builder.Services.AddHttpClient<BuildingOutlineService>();

var app = builder.Build();

Expand Down
2 changes: 2 additions & 0 deletions Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public void ConfigureServices(IServiceCollection services)
services.AddControllers();
services.AddHttpClient();

services.AddSingleton<BuildingOutlineService>();

// Configure CORS to allow requests from React Native frontend
services.AddCors(options =>
{
Expand Down
Loading