def dms_to_decimal(dms_str):
# zamień znaki na spacje
= dms_str.replace("°", " ").replace("'", " ").replace("\"", " ")
dms_str # podziel tekst na części według spacji
= dms_str.split()
parts
= int(parts[0])
degrees = int(parts[1])
minutes = int(parts[2])
seconds = parts[3]
direction
= degrees + (minutes / 60) + (seconds / 3600)
decimal = round(decimal, 4)
decimal
# sprawdź kierunek i nadaj znak
if direction in ["S", "W"]:
= -decimal
decimal
# zwracany jest typ liczbowy
return decimal
Algorytmy danych geoprzestrzennych
Obliczenia geodezyjne
Krzysztof Dyba
Współrzędne geograficzne
Współrzędne geograficzne to zestaw wartości liczbowych używany do określania położenia dowolnego punktu na powierzchni Ziemi. Dwoma podstawowymi składowymi współrzędnych geograficznych są:
- szerokość geograficzna (latitude; \(\phi\)) – określa jak daleko na północ lub południe znajduje się punkt od równika Ziemi. Przyjmuje wartości z przedziału od 0° do 90° na północ (wartości dodatnie) i od 0° do 90° na południe (wartości ujemne) na biegunach.
- długość geograficzna (longitude; \(\lambda\)) – określa jak daleko na wschód lub zachód znajduje się punkt od południka zerowego, który przebiega przez Greenwich w Anglii. Przyjmuje wartości z przedziału od 0° do 180° na wschód (wartości dodatnie) i od 0° do 180° na zachód (wartości ujemne).
Uwaga! Kolejność współrzędnych geograficznych może być różna w zależności od systemu, przyjętego standardu czy oprogramowania, np. norma ISO 6709 definiuje kolejność (szerokość, długość), podczas gdy standardy Open Geospatial Consortium określają ją odwrotnie (patrz GeoJSON czy Well-Known Text).
Współrzędne geograficzne są zazwyczaj wyrażane w jednym z następujących formatów:
- stopnie, minuty i sekundy (degrees, minutes, seconds; DMS) – stopnie reprezentują miarę kątową. Każdy stopień podzielony jest na 60 minut, a każda minuta na 60 sekund. Przykładowo: 52°17’34” N, 16°44’08” E.
- stopnie dziesiętne (decimal degrees; DD) – współrzędne wyrażone są jako wartości dziesiętne stopni. Przykładowo: 52,2927° N, 16,7355° E.
- współrzędne planarne – współrzędne wyrażone jako pomiary liniowe w układzie planarnym. Przykładowo: 345613,2393 m, 494269,4463 m (układ PUWG 1992).
Konwersja
Wartość DMS konwertowana jest na stopnie dziesiętne przy użyciu następującego wzoru:
\[ \text{Stopnie dziesiętne} = \text{Stopnie} + \frac{\text{Minuty}}{60} + \frac{\text{Sekundy}}{3600} \]
print(dms_to_decimal("52°17'34\" N"), "°", sep = "")
print(dms_to_decimal("16°44'08\" E"), "°", sep = "")
52.2928°
16.7356°
Natomiast operacja odwrotna, czyli konwersja stopni dziesiętnych na wartość DMS, wymaga zastosowanie poniższych wzorów:
\[ \text{Stopnie dziesiętne} = |\text{Stopnie dziesiętne}| \]
\[ \text{Stopnie} = \text{int}(\text{Stopnie dziesiętne}) \]
\[ \text{Minuty} = \text{int}\left((\text{Stopnie dziesiętne} - \text{Stopnie}) \times 60\right) \]
\[ \text{Sekundy} = \left((\text{Stopnie dziesiętne} - \text{Stopnie}) \times 60 - \text{Minuty}\right) \times 60 \]
Obliczenia
Odległość
W tej sekcji porównamy różne metody obliczania odległości między punktami uwzględniając:
- odległość euklidesową (układ kartezjański),
- odległość sferyczną,
- odległość elipsoidalną.
W tym celu wykorzystamy dwa punkty reprezentujące lokalizację Warszawy (21,0122°, 52,2296°) oraz Rzymu (12,5113°, 41,8919°).
Euklidesowa
Odległość euklidesowa w przestrzeni dwuwymiarowej definiowana jest jako:
\[ d=\sqrt{(\Delta x)^2+(\Delta y)^2}=\sqrt{(x_2-x_1)^2+(y_2-y_1)^2} \]
Wynika ona bezpośrednio z twierdzenia Pitagorasa o trójkątach prostokątnych:
\[ a^2 + b^2 = c^2, \]
gdzie \(a\) i \(b\) to długość przyprostokątnych, a \(c\) to długość przeciwprostokątnej.
Procedura wymaga zastosowania transformacji współrzędnych z układu elipsoidalnego do kartezjańskiego, aby uzyskać sensowne wyniki. Należy wykorzystać klasę QgsCoordinateTransform
i na przykład odwzorowanie sferyczne Mercatora (EPSG:3857
). Po przeprowadzonej transformacji, można obliczyć odległość między punktami używając metodę distance()
.
from qgis.core import QgsPointXY, QgsCoordinateReferenceSystem
from qgis.core import QgsCoordinateTransform, QgsCoordinateTransformContext
= (21.0122, 52.2296)
pt1 = (12.5113, 41.8919)
pt2
= QgsPointXY(pt1[0], pt1[1])
pt1 = QgsPointXY(pt2[0], pt2[1])
pt2
= QgsCoordinateReferenceSystem("EPSG:4326")
crs_4326 = QgsCoordinateReferenceSystem("EPSG:3857")
crs_3857 = QgsCoordinateTransformContext()
transform_context = QgsCoordinateTransform(crs_4326, crs_3857, transform_context)
transform
= transform.transform(pt1)
pt1_3857 = transform.transform(pt2)
pt2_3857
= pt1_3857.distance(pt2_3857)
distance = round(distance / 1000, 2) # wynik w km
distance
print("Odległość między Warszawą a Rzymem:", distance, "km")
Odległość między Warszawą a Rzymem: 1942.97 km
Możemy również samodzielnie zaimplementować metodę distance()
dla obiektów typu QgsPointXY
w następujący sposób:
def euclidean_distance(pt1, pt2):
import math
# wyodrębnienie współrzędnych z obiektu QgsPoint
= pt1.x(), pt1.y()
x1, y1 = pt2.x(), pt2.y()
x2, y2
= math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 )
distance = round(distance / 1000, 2) # wynik w km
distance return distance
euclidean_distance(pt1_3857, pt2_3857)
1942.97
Sferyczna
Drugie podejście opiera się na wykorzystaniu trygonometrii na sferze, która przybliża powierzchnię Ziemi. W tym przypadku pomijamy transformację układu współrzędnych. Jednakże, do obliczeń zamiast stopni należy wykorzystać radiany. Ten sposób oferuje błąd na poziomie 0,5%.
Wzór jest następujący:
\[ \Delta{x} = \cos(\phi_2)\cos(\lambda_2) - \cos(\phi_1)\cos(\lambda_1) \]
\[ \Delta{y} = \cos(\phi_2)\sin(\lambda_2) - \cos(\phi_1)\sin(\lambda_1) \]
\[ \Delta{z} = \sin(\phi_2) - \sin(\phi_1) \]
\[ d_\textrm{t} = r \sqrt{(\Delta{x})^2 + (\Delta{y})^2 + (\Delta{z})^2} \]
\[ d = 2 r \arcsin \frac{d_\textrm{t}}{2 r}, \]
gdzie \(r\) to średni promień Ziemi równy 6 371 009 m.
Średni promień Ziemi można wyliczyć z poniższego równania:
\[ r = \frac{2a+b}{3}, \]
gdzie \(a\) to promień równikowy wynoszący 6 378 137 m, a \(b\) to promień biegunowy wynoszący 6 356 752 m.
# https://en.wikipedia.org/wiki/Geographical_distance#Spherical-surface_formulae
def spherical_distance(pt1, pt2):
from math import sin, cos, asin, sqrt, pi
= 6371009 # średni promień Ziemi w metrach
r
= [pt1.x(), pt1.y(), pt2.x(), pt2.y()]
coords # konwersja stopni na radiany
= [coord * pi / 180 for coord in coords]
coords = coords
x1, y1, x2, y2
= cos(y2) * cos(x2) - cos(y1) * cos(x1)
delta_x = cos(y2) * sin(x2) - cos(y1) * sin(x1)
delta_y = sin(y2) - sin(y1)
delta_z = r * sqrt( delta_x**2 + delta_y**2 + delta_z**2 )
d_t = 2 * r * asin(d_t / (2 * r))
distance = round(distance / 1000, 2)
distance
return distance
= spherical_distance(pt1, pt2)
distance print("Odległość między Warszawą a Rzymem:", distance, "km")
Odległość między Warszawą a Rzymem: 1315.51 km
Elipsoidalna
Precyzyjniejszy wynik można uzyskać stosując obliczenia na elipsoidzie zamiast sfery (wzór Vincenty’ego). Służy do tego klasa QgsDistanceArea oraz metoda measureLine()
. Należy również wybrać elipsoidę używając metody setEllipsoid()
. Standardowo do pomiarów globalnych wykorzystuje się elipsoidę WGS84
.
from qgis.core import QgsDistanceArea
= QgsDistanceArea()
distance_area "WGS84")
distance_area.setEllipsoid(
= distance_area.measureLine(pt1, pt2)
distance = round(distance / 1000, 2) # wynik w km
distance
print("Odległość między Warszawą a Rzymem:", distance, "km")
Odległość między Warszawą a Rzymem: 1316.2 km
Powierzchnia
Sprawdźmy teraz jakie różnice zachodzą w przypadku pomiarów powierzchni w układzie planarnym (EPSG:2180
) oraz na elipsoidzie. Jako dane wejściowe wykorzystajmy przybliżoną obwiednię Polski.
Kartezjańska
Procedura wygląda identycznie jak w przypadku obliczania odległości między punktami z tą różnicą, iż należy wykorzystać metodę area()
.
from qgis.core import QgsGeometry
= "POLYGON((14 49, 14 54, 24 54, 24 49, 14 49))"
bbox = QgsGeometry.fromWkt(bbox)
geom
= QgsCoordinateReferenceSystem("EPSG:4326")
crs_4326 = QgsCoordinateReferenceSystem("EPSG:2180")
crs_2180 = QgsCoordinateTransformContext()
transform_context = QgsCoordinateTransform(crs_4326, crs_2180, transform_context) transform
Uwaga! Transformacja geometrii jest dokonana bezpośrednio na obiekcie klasy QgsGeometry
.
# EPSG:2180
geom.transform(transform) print(geom.asWkt(precision = 1))
Polygon ((134461.7 137878.5, 172479.3 693299.8, 827520.7 693299.8, 865538.3 137878.5, 134461.7 137878.5))
= geom.area()
area = round(area / 1000**2, 2) # wynik w km^2
area
print("Powierzchnia poligonu wynosi:", area, "km^2")
Powierzchnia poligonu wynosi: 384939.74 km^2
Elipsoidalna
Do obliczenia powierzchni elipsoidalnej należy użyć metody measureArea()
, tak jak zademonstrowano to w poprzedniej sekcji na przykładzie odległości elipsoidalnej.
= QgsGeometry.fromWkt(bbox) # EPSG:4326
geom
= QgsDistanceArea()
distance_area "WGS84")
distance_area.setEllipsoid(
= distance_area.measureArea(geom)
area = round(area / 1000**2, 2)
area
print("Powierzchnia poligonu wynosi:", area, "km^2")
Powierzchnia poligonu wynosi: 385343.21 km^2
Zadania
- Zaimplementuj funkcję
decimal_to_dms()
umożliwiającą konwersję stopni dziesiętnych na wartość DMS. W funkcji uwzględnij argument logicznyis_lat
, który pozwoli określić czy współrzędna wejściowa reprezentuje szerokość czy długość geograficzną i zwróci wynik z odpowiednim kierunkiem geograficznym. - Zaimplementuj wzór haversine [9] jako funkcję i porównaj z innymi otrzymanymi wynikami na przykładzie odległości pomiędzy Warszawą a Rzymem. Współrzędne geograficzne wyrażone w stopniach muszą zostać zamienione na radiany.
- Zaimplementuj funkcję
surface_distance()
, która obliczy odległość powierzchniową między punktami w przestrzeni trójwymiarowej. Następnie wykorzystaj ją do obliczenia całkowitej długości profilu terenu zapisanego w plikuprofil.csv
w układzieEPSG:2180
. Do wczytania plikucsv
można wykorzystać moduł csv lub klasę QgsVectorLayer w QGIS.