Comment fonctionne ST_Area dans PostGIS?

9

J'effectue un calcul simple sur un polygone connu pour avoir une superficie d'environ 6226 km ^ 2. Il est stocké dans une colonne Géographie (WGS84 SRID).

La requête est:

select st_astext(col), st_area(col) area from table

et retourne:

"POLYGON((-180 58.282525588539,-178.916399160189 57.4759784390599,-178.191728834624 58.5761461944577,-180 58.282525588539))" | 5807028547.33813

La zone retournée (5807028547.33813) semble être mm ^ 2 et non km ^ 2? La documentation http://postgis.net/docs/ST_Area.html indique "par défaut, la zone est déterminée sur un sphéroïde avec des unités en mètres carrés"

S'agit-il d'une erreur de documentation ou est-ce que ce qui précède est correct et je comprends fondamentalement mal la fonctionnalité?

J Tileson
la source

Réponses:

4

Le calcul donne la bonne sortie. Comme vous l'avez cité, la documentation indique que "la zone par défaut est déterminée sur un sphéroïde avec des unités en mètres carrés".

Le résultat de votre requête est 5807028547.33813 m ^ 2 . Pour obtenir la superficie en km ^ 2, vous devez diviser le résultat par 1 000 000.

5807028547.33813 m^2 / 1,000,000 = 5807.02854733813 km^2

5807,02854733813 km ^ 2 correspond approximativement à vos 6226 km ^ 2 prévus.

yxcv
la source
C'est correct. Pour référence: postgis.net/docs/ST_Area.html
alphabetasoup
Mais quelle est la base de la division par 100 000? km ^ 2 = m ^
2/1
C'est une faute de frappe. La valeur devrait être 1.0e + 006, mais le résultat était juste (5807 km ^ 2) mm ^ 2 aurait été encore 1.0e + 006 plus grand.
Vince
@Vince Vous avez raison. J'ai corrigé la faute de frappe.
yxcv
@ J Tilesone La question de la base de cette division que vous pouvez poser sur math.stackexchange.com .
yxcv
6

SELECT
     st_astext (col)
    , st_area (col, false ) AS area
FROM table

ST_Area (géométrie) calcule la zone du polygone au format WGS1984, SANS projeter sur une sphère / ellipse de surface égale (si vous utilisez la géométrie de type sql au lieu de la géographie). Le résultat est mesuré dans l'unité dans le SRID de la géométrie.

ST_Area (géographie) calcule la zone du polygone au format WGS1984, AVEC projection sur une sphère / ellipse de surface égale (si vous utilisez la géographie de type sql au lieu de la géométrie). Le résultat est mesuré en mètres carrés. Pour passer du m 2 au km 2 , il faut diviser le m 2 par 1000 2 (1000 mètres par kilomètre - il est carré car c'est une zone, donc 1000 * 1000 aka 1000 2 ).

ST_Area (géométrie, vrai / faux) calcule l'aire (en m 2 ) avec les coordonnées projetées dans le système de coordonnées CylindricalEqualAreaworld (préservation de l'aire - est logique si vous souhaitez calculer l'aire).

La différence entre vrai / faux est la précision.
ST_Area (geog, false) utilise une sphère plus rapide mais moins précise.

Dis, quand j'utilise ce polygone:

var poly = [
    [47.3612503, 8.5351944],
    [47.3612252, 8.5342631],
    [47.3610145, 8.5342755],
    [47.3610212, 8.5345227],
    [47.3606405, 8.5345451],
    [47.3606350, 8.5343411],
    [47.3604067, 8.5343545],
    [47.3604120, 8.5345623],
    [47.3604308, 8.5352457],
    [47.3606508, 8.5352328],
    [47.3606413, 8.5348784],
    [47.3610383, 8.5348551],
    [47.3610477, 8.5352063],
    [47.3612503, 8.5351944]
];

J'obtiens les résultats suivants:

ST_Area(g) =             5.21556075001092E-07
ST_Area(g, false)     6379.25032051953
ST_Area(g, true)      6350.65051177517

Je pense que la partie importante à tirer des documents est la suivante:

Pour la géométrie , une zone cartésienne 2D est déterminée avec des unités spécifiées par le SRID.
Pour géo graphie , par zone par défaut est déterminée sur un ellipsoïde avec des unités en mètres carrés .

Vous devez donc faire attention à choisir la géographie et non la géométrie.
Si vous utilisez la géométrie, vous DEVEZ utiliser les surcharges vraies / fausses de ST_Area.

En C #, j'obtiens plus ou moins la même chose que true avec KnownCoordinateSystems.Projected.World.CylindricalEqualAreaworld, et false semble être un monde à rayon moyen de la terre, quelque chose de proche de WorldSpheroid.CylindricalEqualAreasphere ou WorldSpheroid.EckertIVsphere, mais c'est hors de 2m 2 , il semble donc faire sa propre chose.

using DotSpatial.Projections;
using DotSpatial.Topology;


namespace TestSpatial
{


    static class Program
    {

        // https://stackoverflow.com/questions/46159499/calculate-area-of-polygon-having-wgs-coordinates-using-dotspatial
        // pfff wrong...
        public static void TestPolygonArea()
        {
            // this feature can be see visually here http://www.allhx.ca/on/toronto/westmount-park-road/25/
            string feature = "-79.525542519049552,43.691278124243432 -79.525382520578987,43.691281097414787 -79.525228855617627,43.69124858593392 -79.525096151437353,43.691183664769774 -79.52472799258571,43.690927163079735 -79.525379447437814,43.690771996666641 -79.525602330675355,43.691267524226838 -79.525542519049552,43.691278124243432";
            feature = "47.3612503,8.5351944 47.3612252,8.5342631 47.3610145,8.5342755 47.3610212,8.5345227 47.3606405,8.5345451 47.3606350,8.5343411 47.3604067,8.5343545 47.3604120,8.5345623 47.3604308,8.5352457 47.3606508,8.5352328 47.3606413,8.5348784 47.3610383,8.5348551 47.3610477,8.5352063 47.3612503,8.5351944";

            string[] coordinates = feature.Split(' ');
            // System.Array.Reverse(coordinates);


            // dotspatial takes the x,y in a single array, and z in a separate array.  I'm sure there's a 
            // reason for this, but I don't know what it is.'
            double[] xy = new double[coordinates.Length * 2];
            double[] z = new double[coordinates.Length];
            for (int i = 0; i < coordinates.Length; i++)
            {
                double lon = double.Parse(coordinates[i].Split(',')[0]);
                double lat = double.Parse(coordinates[i].Split(',')[1]);
                xy[i * 2] = lon;
                xy[i * 2 + 1] = lat;
                z[i] = 0;
            }

            double area = CalculateArea(xy);
            System.Console.WriteLine(area);
        }



        public static double CalculateArea(double[] latLonPoints)
        {
            // source projection is WGS1984
            ProjectionInfo projFrom = KnownCoordinateSystems.Geographic.World.WGS1984;
            // most complicated problem - you have to find most suitable projection
            ProjectionInfo projTo = KnownCoordinateSystems.Projected.UtmWgs1984.WGS1984UTMZone37N;
            projTo = KnownCoordinateSystems.Projected.Europe.EuropeAlbersEqualAreaConic; // 6350.9772005155683
            // projTo= KnownCoordinateSystems.Geographic.World.WGS1984; // 5.215560750019806E-07
            projTo = KnownCoordinateSystems.Projected.WorldSpheroid.EckertIVsphere; // 6377.26664171461
            projTo = KnownCoordinateSystems.Projected.World.EckertIVworld; // 6391.5626849671826
            projTo = KnownCoordinateSystems.Projected.World.CylindricalEqualAreaworld; // 6350.6506013739854
            projTo = KnownCoordinateSystems.Projected.WorldSpheroid.CylindricalEqualAreasphere; // 6377.2695087222382
            projTo = KnownCoordinateSystems.Projected.WorldSpheroid.EquidistantCylindricalsphere; // 6448.6818862780929
            projTo = KnownCoordinateSystems.Projected.World.Polyconicworld; // 8483.7701716953889
            projTo = KnownCoordinateSystems.Projected.World.EquidistantCylindricalworld; // 6463.1380225215107
            projTo = KnownCoordinateSystems.Projected.World.EquidistantConicworld; // 8197.4427198320627
            projTo = KnownCoordinateSystems.Projected.World.VanderGrintenIworld; // 6537.3942984174937
            projTo = KnownCoordinateSystems.Projected.World.WebMercator; // 6535.5119516421109
            projTo = KnownCoordinateSystems.Projected.World.Mercatorworld; // 6492.7180733950809
            projTo = KnownCoordinateSystems.Projected.SpheroidBased.Lambert2; // 9422.0631835013628
            projTo = KnownCoordinateSystems.Projected.SpheroidBased.Lambert2Wide; // 9422.0614012926817
            projTo = KnownCoordinateSystems.Projected.TransverseMercator.WGS1984lo33; // 6760.01638841012
            projTo = KnownCoordinateSystems.Projected.Europe.EuropeAlbersEqualAreaConic; // 6350.9772005155683
            projTo = KnownCoordinateSystems.Projected.UtmOther.EuropeanDatum1950UTMZone37N; // 6480.7883094931021


            // ST_Area(g, false)     6379.25032051953
            // ST_Area(g, true)      6350.65051177517
            // ST_Area(g)            5.21556075001092E-07


            // prepare for ReprojectPoints (it's mutate array)
            double[] z = new double[latLonPoints.Length / 2];
            // double[] pointsArray = latLonPoints.ToArray();

            Reproject.ReprojectPoints(latLonPoints, z, projFrom, projTo, 0, latLonPoints.Length / 2);

            // assemblying new points array to create polygon
            System.Collections.Generic.List<Coordinate> points = 
                new System.Collections.Generic.List<Coordinate>(latLonPoints.Length / 2);

            for (int i = 0; i < latLonPoints.Length / 2; i++)
                points.Add(new Coordinate(latLonPoints[i * 2], latLonPoints[i * 2 + 1]));

            Polygon poly = new Polygon(points);
            return poly.Area;
        }


        [System.STAThread]
        static void Main(string[] args)
        {
            TestPolygonArea();

            System.Console.WriteLine(System.Environment.NewLine);
            System.Console.WriteLine(" --- Press any key to continue --- ");
            System.Console.ReadKey();
        }
    }
}

par exemple, vous obtenez un ajustement serré à faux avec le rayon moyen:

// https://gis.stackexchange.com/a/816/3997
function polygonArea()
{
    var poly = [
        [47.3612503, 8.5351944],
        [47.3612252, 8.5342631],
        [47.3610145, 8.5342755],
        [47.3610212, 8.5345227],
        [47.3606405, 8.5345451],
        [47.3606350, 8.5343411],
        [47.3604067, 8.5343545],
        [47.3604120, 8.5345623],
        [47.3604308, 8.5352457],
        [47.3606508, 8.5352328],
        [47.3606413, 8.5348784],
        [47.3610383, 8.5348551],
        [47.3610477, 8.5352063],
        [47.3612503, 8.5351944]
    ];


    var area = 0.0;
    var len = poly.length;

    if (len > 2)
    {

        var p1, p2;

        for (var i = 0; i < len - 1; i++)
        {

            p1 = poly[i];
            p2 = poly[i + 1];

            area += Math.radians(p2[0] - p1[0]) *
                (
                    2
                    + Math.sin(Math.radians(p1[1]))
                    + Math.sin(Math.radians(p2[1]))
                );
        }

        // https://en.wikipedia.org/wiki/Earth_radius#Equatorial_radius
        // https://en.wikipedia.org/wiki/Earth_ellipsoid
        // The radius you are using, 6378137.0 m corresponds to the equatorial radius of the Earth.
        var equatorial_radius = 6378137; // m
        var polar_radius = 6356752.3142; // m
        var mean_radius = 6371008.8; // m
        var authalic_radius = 6371007.2; // m (radius of perfect sphere with same surface as reference ellipsoid)
        var volumetric_radius = 6371000.8 // m (radius of a sphere of volume equal to the ellipsoid)
        // geodetic latitude φ
        var siteLatitude = Math.radians(poly[0][0]);


        // https://en.wikipedia.org/wiki/Semi-major_and_semi-minor_axes
        // https://en.wikipedia.org/wiki/World_Geodetic_System
        var a = 6378137; // m
        var b = 6356752.3142; // m
        // where a and b are, respectively, the equatorial radius and the polar radius.

        var R1 = Math.pow(a * a * Math.cos(siteLatitude), 2) + Math.pow(b * b * Math.sin(siteLatitude), 2)
        var R2 = Math.pow(a * Math.cos(siteLatitude), 2) + Math.pow(b * Math.sin(siteLatitude), 2);

        // https://en.wikipedia.org/wiki/Earth_radius#Radius_at_a_given_geodetic_latitude
        // Geocentric radius
        var R = Math.sqrt(R1 / R2);
        // var merid_radius = ((a * a) * (b * b)) / Math.pow(Math.pow(a * Math.cos(siteLatitude), 2) + Math.pow(b * Math.sin(siteLatitude), 2), 3/2)



        // console.log(R);
        // var hrad = polar_radius + (90 - Math.abs(siteLatitude)) / 90 * (equatorial_radius - polar_radius);
        var radius = mean_radius;

        area = area * radius * radius / 2.0;
    } // End if len > 0

    // equatorial_radius: 6391.565558418869 m2
    // mean_radius:       6377.287126172337m2
    // authalic_radius:   6377.283923019292 m2
    // volumetric_radius: 6377.271110415153 m2
    // merid_radius:      6375.314923754325 m2
    // polar_radius:      6348.777989748668 m2
    // R:                 6368.48180842528 m2
    // hrad:              6391.171919886588 m2

    // http://postgis.net/docs/doxygen/2.2/dc/d52/geography__measurement_8c_a1a7c48d59bcf4ed56522ab26c142f61d.html
    // ST_Area(false)     6379.25032051953
    // ST_Area(true)      6350.65051177517

    // return area;
    return area.toFixed(2);
}

WebMercator est le système de coordonnées utilisé par Google-Maps.
Le nom officiel de ce système de coordonnées est EPSG: 3857.

Ce que fait exactement PostGIS, est documenté ici:
https://postgis.net/docs/ST_Area.html

Et les détails dans le code source peuvent être trouvés ici:
http://postgis.net/docs/doxygen/2.2/dc/d52/geography__measurement_8c_a1a7c48d59bcf4ed56522ab26c142f61d.html

et ici:
http://postgis.net/docs/doxygen/2.2/d1/dc0/lwspheroid_8c_a29d141c632f6b46587dec3a1dbe3d176.html#a29d141c632f6b46587dec3a1dbe3d176

Albers-Projection: Albers-Projection Albers-Projection 2

Projection cylindrique à surface égale: Cylindrique Cylindrique 2

Stefan Steiger
la source
Stefan, c'est bizarre. Votre remarque et les résultats de calcul réels pour le cas "ST_Area (geography)", donc sans spécifier le booléen "use_spheroid", semblent contredire la documentation officielle de PostGIS. Dans la documentation, il est dit "Pour la géographie, la zone par défaut est déterminée sur un sphéroïde avec des unités en mètres carrés". C'est également le résultat affiché dans le dernier exemple dans la zone grise de la page d'aide, où aucun booléen n'est clairement utilisé dans la commande ST_Area, mais le résultat affiche "sqm_spheroid"? ..
Marco_B
1
Stefan, vos données sources étaient-elles réellement en géographie, ou converties en géographie, ou simplement la géométrie se trouvait-elle en WGS1984 lat / long?
Marco_B
1
@Marco_B: Certainement la géométrie. Sinon, le 5.21556075001092E-07 ne peut pas être expliqué. Je vois, j'aurais dû utiliser la géographie au lieu de la géométrie. voir github.com/ststeiger/AnySqlWebAdmin/blob/master/AnySqlWebAdmin/…
Stefan Steiger
Merci pour la réponse, ça explique alors. Il est peut-être bon de modifier votre message initial, de modifier la déclaration maintenant confuse et quelque peu inexacte de "ST_Area (geography) calcule la zone du polygone en WGS1984, sans projeter sur une sphère / ellipses de zone égale.", Et que cette déclaration ne s'applique qu'à la situation des formes étant en Géométrie, pas en stockage Géographie, et que dans le cas de la Géographie, les formes utiliseront une sphéroïde / sphère.
Marco_B
1
@Marco_B: J'avais déjà commencé à le faire lorsque vous avez écrit ce commentaire. Maintenant c'est fait.
Stefan Steiger