Map is flipped and turned when reading GML data with r - r

I have downloaded a GML-File from this Website.
Then I use the following code to process this file in R:
library(rgdal)
test <- readOGR(dsn='WLV_GZP.gml')
I am printing the map like this:
test3 <- subset(test, qualitativeValue=='Rote Zone Lawine') #For speeding up the Test-print
plot(test3)
The result is turned 90 degrees to the right and flipped (East-West). How can I get this map to work? Any help is greatly appreciated.
edit: The result is the same when using the sf library:
test4 <- sf::st_read('WLV_GZP.gml')
test4 <- subset(test4, qualitativeValue=='Rote Zone Lawine')
plot(test4)
In order to inspect the bbox I transform the map:
roads <- st_transform(test4, crs = 4326)
This yields a bbox:
Dimension: XY
Bounding box: xmin: -23.4819 ymin: 58.59924 xmax: -18.90591 ymax: 62.56157
Geodetic CRS: WGS 84
Since the map displays Austria this is of course way off...
PS: I would like to post a PNG-File but I received an error when uploading the file.

Pass the GDAL SWAP_COORDINATES option. It can take one of three values:
> d_NO = st_read("./WLV_GZP.gml", options=list("SWAP_COORDINATES=NO"))
> d_YES = st_read("./WLV_GZP.gml", options=list("SWAP_COORDINATES=YES"))
> d_AUTO = st_read("./WLV_GZP.gml", options=list("SWAP_COORDINATES=AUTO"))
resulting in, for your data:
> st_bbox(d_NO)
xmin ymin xmax ymax
4285465 2599037 4824292 2888410
> st_bbox(d_YES)
xmin ymin xmax ymax
2599037 4285465 2888410 4824292
> st_bbox(d_AUTO)
xmin ymin xmax ymax
2599037 4285465 2888410 4824292
The d_NO data are in Austria.
I don't know if there's a definitive way of deciding for a given dataset whether to swap or not, the AUTO default should, I imagine, work for a correctly specified data set but if something is out of spec then who knows...

Related

How to calculate the length of borders between countries in R?

In some analyses, it makes sense to use border length as a measure of cultural distance between countries, the idea being that countries that share larger proportions of their borders are more culturally close. This then raises the question of how to compute this. We can grab a shapefile of the world from naturalearthdata.com which covers some 251 units (i.e. they are not all sovereign).
I looked over the methods in the Geocomputation with R ebook website and it seems like an intersection is closest to what we want, i.e. st_intersection(), while st_touches() finds the neighbors without giving any sense of the border length. However, when I try it out on two neighbors, Denmark and Germany, I get no overlap:
> suppressWarnings(library(sf))
Linking to GEOS 3.6.2, GDAL 2.2.3, PROJ 4.9.3; sf_use_s2() is TRUE
> world = read_sf("data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp")
> #make valid otherwise get an error
> world = st_make_valid(world)
> #which countries touch each other by border (of the polygons)
> neighbor_ids = st_touches(
+ world$geometry,
+ world$geometry
+ )
> #Denmark Germany
> (germany_idx = which(world$ADMIN=="Germany"))
[1] 50
> (denmark_idx = which(world$ADMIN=="Denmark"))
[1] 71
> world$ADMIN[neighbor_ids[germany_idx][[1]]]
[1] "France" "Czechia" "Luxembourg" "Belgium" "Denmark" "Poland" "Austria" "Switzerland" "Netherlands"
> world$ADMIN[neighbor_ids[denmark_idx][[1]]]
[1] "Germany"
> #border intersection
> #Denmark Germany border as test
> st_intersection(
+ world$geometry[germany_idx],
+ world$geometry[denmark_idx]
+ )
Geometry set for 0 features
Bounding box: xmin: NA ymin: NA xmax: NA ymax: NA
CRS: 4326
How does one get the border lengths? According to Wikipedia, it should be 68 km.
It seems that what is needed is to tell st_intersection() to include the line at the border. By default, this 1 point overlap is ignored, I guess because it has a 0 area. This functionality is controlled by the ... which forwards to s2_options(). The right parameter is model, which defaults to "open", but should be "closed". Thus:
> #include the line
> st_intersection(
+ world$geometry[germany_idx],
+ world$geometry[denmark_idx],
+ model = "closed"
+ )
Geometry set for 1 feature
Geometry type: MULTILINESTRING
Dimension: XY
Bounding box: xmin: 8.660776 ymin: 54.80162 xmax: 9.437503 ymax: 54.9059
CRS: 4326
MULTILINESTRING ((9.436922 54.81014, 9.422143 5...
To get the length, just add on st_length():
> st_intersection(
+ world$geometry[germany_idx],
+ world$geometry[denmark_idx],
+ mode = "closed"
+ ) %>% st_length()
111563 [m]
Only the result is wrong! The scaling is off by factor of 1.64 or so.
Potential problems:
Is this an issue with the coastline paradox?
Some kind of incorrect setting? I the only setting for st_distance() is the size of the earth, which seems to be set correctly.
Bad shapefile? I downloaded a different one (I forgot the source), and it produced a result of 113469 m, which is slightly different but not remotely close to 68000 as Wikipedia gives.
Is it due to a water border? I plotted the border with tmap, and it looks fine.
This is an interesting problem; I believe the coastline paradox plays a role, but only a minor one. The chief issue seems to be driven by CRS.
Let me illustrate on three examples using the world dataset provided by GISCO (i.e. Eurostat). I like this dataset as it allows several levels of precision.
a rough map in EPSG:3035 (the official CRS for continental EU)
a fine map in EPSG:3035
the same fine map in EPSG:4326 / WGS84
Compare these with the official, i.e. wikipedia length of 68 kilometers.
The rough map is off by about 1/6th, which is to be expected given the low resolution. The fine map is quite close (7% off), and you could expect the actual length to increase yet more, as 1:1M is still a coarse map.
On the other hand the length of the same fine map as in previous example, but projected in WGS84, is off by a factor of two, as you observed.
library(sf)
library(dplyr)
library(giscoR)
# rough line / resolution 1:60 000 000
rough <- gisco_get_countries(resolution = "60",
epsg = 3035,
country = c("DE", "DK"))
plot(st_geometry(rough))
sf::st_intersection(rough[1, ], rough[2, ], model = "closed") %>%
mutate(border_length = st_length(.)) %>%
pull(border_length)
# 53141.03 [m]
# fine line / resolution 1:1 000 000
fine <- gisco_get_countries(resolution = "01",
epsg = 3035,
country = c("DE", "DK"))
plot(st_geometry(fine))
sf::st_intersection(fine[1, ], fine[2, ], model = "closed") %>%
mutate(border_length = st_length(.)) %>%
pull(border_length)
# 63795.4 [m]
# fine line in WGS84
fine_wgs <- gisco_get_countries(resolution = "01",
epsg = 4326,
country = c("DE", "DK"))
sf::st_intersection(fine_wgs[1, ], fine_wgs[2, ], model = "closed") %>%
mutate(border_length = st_length(.)) %>%
pull(border_length)
# 127223 [m]
EDIT (2022-09-12) on second thought this seems to be affected by the behavior of the S2 engine behind {sf} (turning it off via sf_use_s2(FALSE) leads to more reasonable length of borders even for data projected in WGS84).
I will raise it as an issue with {sf} maintainers, as it does not seem likely that this is an expected behaviour.

Smoothing polygons on map with ggplot2 and sf

How can you smooth the polygons of a map produced with ggplot and sf?
I have used the sf package to extract the polygons from a shapefile
geomunicipios <- st_read("ruta/archivo.shp")
Reading layer `archivo' from data source
`ruta\archivo.shp'
using driver `ESRI Shapefile'
Simple feature collection with 45 features and 10 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -2.344411 ymin: 37.37375 xmax: -0.647983 ymax: 38.75509
Geodetic CRS: WGS 84
And ggplot2 to plot the map:
rmurcia <- ggplot(data = geomunicipios) +
geom_sf(aes(fill=columna),color="#FFFFFF",size=1)
To perform the smoothing of the polygons I have analyzed three alternatives:
i. package "smoothr":
geosmunicipios <- smooth(geomunicipios, method = "ksmooth", smoothness = 12)
ii. package "rmapshaper": geosmunicipios <- ms_simplify(geomunicipios, keep = 0.02500, weighting = 12)
iii. package "sf": geosmunicipios <- st_simplify(geomunicipios, dTolerance = 50, preserveTopology = TRUE)
You have to try different values of the parameters to adjust to the needs and obtain the desired result.
To reproduce the case, the download can be done from: centrodedescargas.cnig.es/CentroDescargas/index.jsp
And follow the links:
Información geográfica de referencia - Límites municipales, provinciales y autonómicos - Descargar: lineas_limite.zip.
And the path in the uncompressed folder:
SIGLIM_Publico_INSPIRE - SHP_ETRS89 - recintos_municipales_inspire_peninbal_etrs89 - recintos_municipales_inspire_peninbal_etrs89.shp
Finally, for this case I have chosen to use rmapshaper, it produces a satisfactory result with a reduced size of the .pdf file, where I include the graphic.

Cleaning Geocode data with r

I am cleaning my dataset and I don't know how to clean GPS data.
when I use the table function I find that they are entered in different shapes.
"547140",
"35.6997",
"251825.7959",
"251470.43",
"54/4077070001",
and "54/305495"
I don't know how to clean this variable with this great difference.
I would be thankful if help me or suggest me a website for training.
Your main issue is standardizing the GPS by projecting GPS to a coordinate system of choice. Say we have the GPS of amsterdam in two different coordinate systems, one in amersfoort/rd new (espg 28992) and one in wsg1984 (espg 4326):
x y location espg
1: 1.207330e+05 486632.35593 amsterdam 28992
2: 4.884088e+00 52.36651 amsterdam 4326
structure(list(x = c(120733.012428048, 4.88408811380055), y = c(486632.355933105,
52.3665054922233), location = c("amsterdam", "amsterdam"), espg = c(28992,
4326)), row.names = c(NA, -2L), class = "data.frame")
What we want to do is reproject our coordinates to one geographic coordinate system of choice. In this case I used WSG1984 (espg 4326).
library(sf)
#here I tell R which columns contain the coordinates
coordinates(dt) <- ~x+y
#I now convert the table to a spatial object
dt <- st_as_sf(dt)
#here I split by the different ESPG's present
dt <- split(dt, dt$espg)
#here I loop through every individual espg present in the dataset
for(i in 1:length(dt)){
#here I say in which coordinate system (espg) the GPS data is in
st_crs(dt[[i]]) <- unique(dt[[i]]$espg)
#here I transform the coordinates to another projection (in this case WSG1984, espg 4326)
dt[[i]] <- dt[[i]] %>% st_transform(4326)
}
#here I bind the items of the list together
dt <- do.call(rbind, dt)
head(dt)
Simple feature collection with 2 features and 2 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 4.884088 ymin: 52.36651 xmax: 4.884088 ymax: 52.36651
Geodetic CRS: WGS 84
location espg geometry
4326 amsterdam 4326 POINT (4.884088 52.36651)
28992 amsterdam 28992 POINT (4.884088 52.36651)
In the geometry column you now see that the coordinates are equal to one another.
Bottom line is that you need to know the geographic coordinate system the GPS data is in. Then you can convert your data from a table to a spatial object and transform the GPS data to a projection of choice.
In addition, it is always a good idea to check if your assumption on the original ESPG is good by for example plotting the data.
library(ggplot2)
library(ggspatial)
ggplot(dt) + annotation_mape_tile() + geom_sf(size = 4) + theme(text = element_text(size = 15) + facet_wrap(~espg)
In the figurebelow we see that the projection went well for both espg's.

Make a vector of coordinates to filter data within a certain area

Rookie R user here and I would greatly appreciate any help you someone could give me.
My project requires me to create a vector boundary box around a city of my choice and then filter a lot of data so I only have the data relative to the area. However, it is several years since I have used R studio and its fair to say I remember little to nothing about the language.
I have initially used
geocode("Hereford, UK")
bbox <-c(Longitude=-2.72,Latitude=52.1)
myMap <- get_map(location = "Hereford, UK",source="google",maptype="roadmap")
I then must create a new tibble which filters out and gives only the relevant data to the area.
I am unsure how to proceed with this and I then must overlay the data onto the map which I have created.
As I only have a centre point of coordinates, is it possible to create a circle with a radius of say 3 miles around the centre of my location so I can then filter this area?
Thank you all for taking the time to read my post. Cheers!
Most spatial work can now be done pretty easily using the sf package.
Example code for a similar problem is below. The comments explain most of what it does.
The difficult part may be in understanding map projections (the crs). Some use units(meters, feet, etc) and others use latitude / longitude. Which one you choose depends on what area of the globe you're working with and what you're trying to accomplish. Most web mapping uses crs 4326, but that does not include an easily usable distance measurement.
The map below shows points outside ~3 miles from Hereford as red, and those inside in dark maroon. The blue point is used as the center for Hereford & the buffer zone.
library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.2, GDAL 2.2.3, PROJ 4.9.3
library(mapview)
set.seed(4)
#hereford approx location, ggmap requires api key
hereford <- data.frame(place = 'hereford', lat = -2.7160, lon = 52.0564) %>%
st_as_sf(coords = c('lat', 'lon')) %>% st_set_crs(4326)
#simulation of data points near-ish hereford
random_points <- data.frame(point_num = 1:20,
lat = runif(20, min = -2.8, max = -2.6),
lon = runif(20, min = 52, max = 52.1)) %>%
st_as_sf(coords = c('lat', 'lon')) %>% st_set_crs(4326) %>%st_transform(27700)
#make a buffer of ~3miles (4800m) around hereford
h_buffer <- hereford %>% st_transform(27700) %>% #change crs to one measured in meters
st_buffer(4800)
#only points inside ~3mi buffer
points_within <- random_points[st_within( random_points, h_buffer, sparse = F), ]
head(points_within)
#> Simple feature collection with 6 features and 1 field
#> geometry type: POINT
#> dimension: XY
#> bbox: xmin: 346243.2 ymin: 239070.3 xmax: 355169.8 ymax: 243011.4
#> CRS: EPSG:27700
#> point_num geometry
#> 1 1 POINT (353293.1 241673.9)
#> 3 3 POINT (349265.8 239397)
#> 4 4 POINT (349039.5 239217.7)
#> 6 6 POINT (348846.1 243011.4)
#> 7 7 POINT (355169.8 239070.3)
#> 10 10 POINT (346243.2 239690.3)
#shown in mapview
mapview(hereford, color = 'blue') +
mapview(random_points, color = 'red', legend = F, col.regions = 'red') +
mapview(h_buffer, legend = F) +
mapview(points_within, color = 'black', legend = F, col.regions = 'black')
Created on 2020-04-12 by the reprex package (v0.3.0)

st_simplify dTolerence with decimal degree

I'm trying to reduce the size of sf object by applying st_simplify. CRS is 4267 and try to play around with the right level of dTolerance. I understand that the unit of dTolerance has to be that of the CRS, so I started with 0.1, but I constantly getting this error message.
test <- st_read("comm_sf.shp") %>%
+ st_simplify(preserveTopology = T,
+ dTolerance = 0.1)
Simple feature collection with 11321 features and 21 fields
geometry type: MULTIPOLYGON
dimension: XY
bbox: xmin: -124.4375 ymin: 24.5441 xmax: -66.94983 ymax: 49.00249
epsg (SRID): 4326
proj4string: +proj=longlat +datum=WGS84 +no_defs
Warning message:
In st_simplify.sfc(st_geometry(x), preserveTopology, dTolerance) :
st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees
I play around with both setting dTolerance = 1000 (in case it's in meters) and dTolerance = 0.1 (in case it's in long/lat), but I get the same error message. This happens with CRS = 4267 as well. How can I fix this?
Well its a warning rather than an error. But in general you should do Douglas-Peucker on a projected coordinate system - because it uses a distance as a buffer, whereas the actual size of a unit of longitude varies with latitude. Note that the unit used by st_simplify tolerance will always be in the same as the map units.
Here's a reproducible example:
library(sf)
library(maptools)
states = st_as_sf(maps::map("state", plot = FALSE, fill = TRUE))
states_simple = st_simplify(states)
##Warning message:
## In st_simplify.sfc(st_geometry(x), preserveTopology, dTolerance) :
## st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees
But if we transform to a projected coordinate system first, then no warning:
states = st_transform(states, 54032) #azimuthal equidistant
states_simple = st_simplify(states)
You can always go back to WGS84 lat-long after the simplification
states = st_transform(states, 4326)

Resources