-
Notifications
You must be signed in to change notification settings - Fork 0
polygons i kart + egen route close #63 #328
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
base: develop
Are you sure you want to change the base?
Changes from all commits
ed60b24
8d461e3
e536eaa
2e8990b
2a91a2c
bbaf30d
1ae27e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -382,4 +382,37 @@ private bool IsValidCoordinatePrecisionRange(int from, int to) | |
| /// </summary> | ||
| private object CreateRangeErrorMessage(int min, int max) | ||
| => new { error = $"Value must be between {min} and {max}." }; | ||
|
|
||
| /// <summary> | ||
| /// Retrieves polygon geometries from the Location table for observations matching the filter. | ||
| /// Only Polygon and MultiPolygon geometry types are returned. | ||
| /// Rectangular/square polygons (grid-cell precision squares) are excluded automatically. | ||
| /// </summary> | ||
| [HttpPost("LocationPolygons")] | ||
| [Produces("application/json")] | ||
| [ProducesResponseType(typeof(IEnumerable<LocationPolygonDto>), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(StatusCodes.Status400BadRequest)] | ||
| public async Task<ActionResult<IEnumerable<LocationPolygonDto>>> GetLocationPolygons( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kanskje greit å sende med envelopen som vi gjør med punkter? Da henter vi ikke alle polygonene, men bare de som vises på skjermen. |
||
| [FromBody] LocationSearchFilterDto? filter = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| filter ??= new LocationSearchFilterDto(); | ||
|
|
||
| if (!ValidateLocationSearchFilter(filter, out var validationError)) | ||
| { | ||
| return validationError!; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| var polygons = await _searchService.GetLocationPolygonsAsync(filter, cancellationToken); | ||
| _logger.LogInformation("Retrieved {Count} location polygons", polygons.Count()); | ||
| return Ok(polygons); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "Feil ved henting av lokasjonspolygoner"); | ||
| throw; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| namespace Artskart3.Core.Application.DTOs; | ||
|
|
||
| /// <summary> | ||
| /// Represents a location's polygon geometry. | ||
| /// </summary> | ||
| public class LocationPolygonDto | ||
| { | ||
| public int LocationId { get; set; } | ||
| public string? Locality { get; set; } | ||
| public string WktPolygon { get; set; } = null!; | ||
| public int ObservationCount { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -692,4 +692,90 @@ private List<Area> FilterAreasBySelection(List<Area> areas, LocationSearchFilter | |
| ).ToList(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Henter polygon-geometrier fra Location-tabellen for observasjoner som matcher filteret. | ||
| /// Rektangulære polygoner (nøyaktig 5 punkter i ytre ring = rutenett-firkanter) filtreres bort. | ||
| /// </summary> | ||
| public async Task<IEnumerable<LocationPolygonDto>> GetLocationPolygonsAsync(LocationSearchFilterDto? filter = null, CancellationToken cancellationToken = default) | ||
| { | ||
| try | ||
| { | ||
| filter ??= new LocationSearchFilterDto(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vi burde kanskje være litt mer aggresive på antall resultater for polygoner. Vil tro 100 000 polygoner er litt mye, så vi kunne ha hatt lavere maxresults for disse? |
||
|
|
||
| var query = BuildLocationsQuery(filter); | ||
| var polygonMaxResults = filter.MaxResults > 0 | ||
| ? Math.Min(filter.MaxResults, SearchConstants.MaxPolygonResults) | ||
| : SearchConstants.DefaultMaxPolygons; | ||
| var aggregated = await AggregateLocationObservations(query, polygonMaxResults, cancellationToken); | ||
|
|
||
| if (aggregated.Count == 0) return []; | ||
|
|
||
| var locationIds = aggregated.Select(x => x.LocationId).ToList(); | ||
|
|
||
| var locations = await _context.Set<Location>() | ||
| .AsNoTracking() | ||
| .Where(l => locationIds.Contains(l.Id) && l.Geometry != null && (l.Geometry.GeometryType == "Polygon" || l.Geometry.GeometryType == "MultiPolygon")) | ||
| .Select(l => new { l.Id, l.Locality, l.Geometry }) | ||
| .ToListAsync(cancellationToken); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hent ut kun felter vi trenger: Da kan vi også forenkle foreach loopen litt: |
||
|
|
||
| if (locations.Count == 0) return []; | ||
|
|
||
| var countLookup = aggregated.ToDictionary(x => x.LocationId, x => x.ObservationCount); | ||
|
|
||
| var result = new List<LocationPolygonDto>(); | ||
|
|
||
| foreach (var location in locations) | ||
| { | ||
| var geo = location.Geometry!; | ||
| var wkt = geo.AsText(); | ||
| if (IsRectangularPolygon(wkt)) continue; | ||
|
|
||
| // Skip polygons whose bounding box doesn't intersect the visible map extent | ||
| if (filter.Envelope != null) | ||
| { | ||
| var env = geo.EnvelopeInternal; | ||
| if (env.MaxX < filter.Envelope.MinX || env.MinX > filter.Envelope.MaxX || | ||
| env.MaxY < filter.Envelope.MinY || env.MinY > filter.Envelope.MaxY) | ||
| continue; | ||
| } | ||
|
|
||
| result.Add(new LocationPolygonDto | ||
| { | ||
| LocationId = location.Id, | ||
| Locality = location.Locality, | ||
| WktPolygon = wkt, | ||
| ObservationCount = countLookup.GetValueOrDefault(location.Id) | ||
| }); | ||
| } | ||
|
|
||
| _logger.LogInformation("Location polygon search completed. Returned {Count} polygons", result.Count); | ||
| return result; | ||
| } | ||
| catch (Exception ex) when (ex is not OperationCanceledException) | ||
| { | ||
| _logger.LogError(ex, "Feil ved henting av lokasjonspolygoner"); | ||
| throw new ApplicationException("An error occurred while retrieving location polygons. Please try again later.", ex); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns true when the WKT string represents a rectangular polygon (exactly 5 coordinate pairs in the exterior ring). | ||
| /// </summary> | ||
| private static bool IsRectangularPolygon(string? wkt) | ||
| { | ||
| if (string.IsNullOrEmpty(wkt)) return false; | ||
| var ringStart = wkt.IndexOf('(', wkt.IndexOf('(') + 1); | ||
| var ringEnd = wkt.IndexOf(')', ringStart); | ||
| if (ringStart < 0 || ringEnd < 0) return false; | ||
|
|
||
| var ring = wkt.AsSpan(ringStart + 1, ringEnd - ringStart - 1); | ||
| var commaCount = 0; | ||
| foreach (var ch in ring) | ||
| { | ||
| if (ch == ',') commaCount++; | ||
| } | ||
|
|
||
| return commaCount == 4; | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hvorfor ekskluderer vi rektangler/kvadrat? Er ikke disse sensitive funn som også skal vises?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ekskluder skjulte funn, rektangler/kvadrat, litt usikker skal sjekke
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ser ikke noen grunn til å ekskludere skjulte funn. De skal vises de også