Vinicius Quinafelex Alves

🌐English version

[C#] Zona de uma coordenada em arquivo KML

Arquivos KML são XMLs estruturados com dados geográficos para processamento e visualização. Popularmente usada pelo Google Maps, este formato é considerado um padrão internacional pela OGC, consórcio internacional vinculado ao geoprocessamento, desde 2008.

Zoneamento é um método de divisão territorial, aonde uma superfície é fatiada em polígonos (ou zonas) que não se cruzam. Este método é popular para planejamento urbano e determinados tipos de geoprocessamento.

Para encontrar qual a zona correspondente a uma coordenada, o sistema deve ler todos os polígonos do KML, e para cada polígono, verificar se a coordenada está dentro do polígono. Para isso, este artigo usa a bibilioteca SharpKml.Core, implementado em .NET Standard e licença MIT, para navegação do arquivo KML.

No momento esta biblioteca não possui um método para verificar se um ponto está contido em um polígono, então estamos usando um algoritmo externo. Este algoritmo projeta uma linha baseado em um dos eixos da coordenada, e usa uma flag que alterna entre "dentro" e "fora" toda vez que a linha cruza com um dos vértices do polígono.

Instalação do SharpKml.Core

dotnet add package SharpKml.Core

Função principal

using SharpKml.Dom;
using SharpKml.Engine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
            
public static string GetZone(string kmlFilePath, double latitude, double longitude)
{
    KmlFile kmlFile = LoadKmlFile(kmlFilePath);
    IEnumerable<Placemark> placemarks = EnumeratePlacemarks(kmlFile);

    foreach(Placemark placemark in placemarks)
    {
        IEnumerable<Polygon> polygons = EnumeratePolygons(placemark);

        foreach(Polygon polygon in polygons)
        {
            CoordinateCollection coordinates = GetPolygonCoordinates(polygon);

            if(WithinPolygon(coordinates, latitude, longitude))
            {
                return GetSimpleDataValue(placemark, "ZoneID");
            }
        }
    }

    return null;
}

Métodos auxiliares

private static KmlFile LoadKmlFile(string path)
{
    using (var stream = new FileStream(path, FileMode.Open))
        return KmlFile.Load(stream);
}

private static IEnumerable<Placemark> EnumeratePlacemarks(KmlFile file)
{
    return file.Root.Flatten().OfType<Placemark>();
}

private static IEnumerable<Polygon> EnumeratePolygons(Placemark placemark)
{
    var shape = placemark.Geometry;

    if (shape is Polygon)
    {
        yield return (Polygon)shape;
    }
    else if (shape is MultipleGeometry)
    {
        var multipleGeometry = (MultipleGeometry)shape;

        foreach (var geometry in multipleGeometry.Geometry)
            if (geometry is Polygon)
                yield return (Polygon)geometry;
    }
}

private static CoordinateCollection GetPolygonCoordinates(Polygon polygon)
{
    return polygon.OuterBoundary.LinearRing.Coordinates;
}

private static string GetSimpleDataValue(Placemark placemark, string attributeName)
{
    foreach (var schemaData in placemark.ExtendedData.SchemaData)
        foreach (var simpleData in schemaData.SimpleData)
            if (simpleData.Name == attributeName)
                return simpleData.Text;

    return null;
}

// Source: https://stackoverflow.com/a/14998816
public static bool WithinPolygon(CoordinateCollection coordinateCollection, double latitude, double longitude)
{
    var coordinates = coordinateCollection.ToList();

    bool result = false;
    int j = coordinates.Count - 1;

    for (int i = 0; i < coordinates.Count; i++)
    {
        var pointA = coordinates[i];
        var pointB = coordinates[j];

        if (pointA.Longitude < longitude && pointB.Longitude >= longitude || pointB.Longitude < longitude && pointA.Longitude >= longitude)
        {
            if (pointA.Latitude + (longitude - pointA.Longitude) / (pointB.Longitude - pointA.Longitude) * (pointB.Latitude - pointA.Latitude) < latitude)
            {
                result = !result;
            }
        }

        j = i;
    }

    return result;
}