I’m currently doing a project trying to determine the home ranges of sharks using minimum convex polygons as a measurement. So far I’ve managed to successfully make the minimum convex polygons (MCPs) which tell you how large the home range for each animal is, however I’m running into problems when trying to place them on a map, where the map is created but the MCPs are plotted in the completely incorrect area of the world due to issues with coordinate transformations.
I think I’ve narrowed down the problem to the part of my code when I convert the data from a UTM 40 zone to a WGS84 CRS (coordinate system) to make it compatible with Google Maps which uses the WGS84 CRS. The coordinates my MCPs should be in are approximately 71.96, -5.24 which are located in the Chagos Archipelago. However, these coordinates change to 52.51, -4.72e-05 when I convert the data to the new CRS for use with the Google Maps. These new coordinates are very small and not in the Chagos area at all.
The first couple lines of code are converting the MCP data (named SP_cat2 and SP_cat2MCP) into EPSG:32640 (named SF32map_cat2 / cat2MCP), which is the UTM zone 40 / Chagos. When I check the coordinates of these outputs, they are correct:
SF32map_cat2 <- st_as_sf(SP_cat2, crs = 32640)
SF32map_cat2MCP<- st_as_sf(SP_cat2MCP, crs = 32640)
However, when I convert them to EPSG:4326 (to be used with Google Maps) and check the coordinates, they have changed to the very small coordinates. This is the code used:
SFmap_cat2 <- st_transform(SF32map_cat2, crs = 4326)
SFmap_cat2MCP <- st_transform(SF32map_cat2MCP, crs = 4326)
I’ve also used st_as_sf()
for the second lot of code, and this does successfully put the MCPs on a map, but again, with the wrong coordinates.
After converting to the new CRS, the coordinates are completely wrong and this messes up the map as it either plots the MCPs very close to the equator which isn’t where they should be at all, or plots just the Chagos Archipelago with none of my data on top. This is the mapping code in case this is problematic as well:
ggmap(chagos_map) +
geom_sf(data = SFmap_cat2, aes(color = as.factor(code)), size = 2, inherit.aes = FALSE) +
geom_sf(data = SFmap_cat2MCP, fill = NA, color = "black", alpha = 0.5, inherit.aes = FALSE) +
ggtitle("MCP Silvertip Cat 2 Chagos") +
theme_minimal()
I’ve plotted the MCPs themselves without the map behind them and they do plot correctly, but they are located in the complete wrong coordinates very close to the equator. I’ve double checked all the classes and structures of the data and they are all correct. I can’t see where the problem is at all.
Here are the outputs of SP_cat2:
new("SpatialPointsDataFrame", data = structure(list(code = c("12964",
"12964", "12964", "12964", "12964", "12964")), row.names = c(NA,
6L), class = "data.frame"), coords.nrs = 2:3, coords = structure(c(71.9684,
71.9684, 71.9684, 71.9684, 71.9684, 71.9698, -5.2417, -5.2417,
-5.2417, -5.2417, -5.2417, -5.3302), dim = c(6L, 2L), dimnames = list(
NULL, c("receiver_lon", "receiver_lat"))), bbox = structure(c(71.9684,
-5.3302, 71.9698, -5.2417), dim = c(2L, 2L), dimnames = list(
c("receiver_lon", "receiver_lat"), c("min", "max"))), proj4string = new("CRS",
projargs = "+proj=utm +zone=40 +datum=WGS84 +units=m +no_defs"))
and SP32map_cat2MCP:
structure(list(code = c("12964", "12964", "12964", "12964", "12964",
"12964"), geometry = structure(list(structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9698, -5.3302
), class = c("XY", "POINT", "sfg"))), class = c("sfc_POINT",
"sfc"), precision = 0, bbox = structure(c(xmin = 71.9684, ymin = -5.3302,
xmax = 71.9698, ymax = -5.2417), class = "bbox"), crs = structure(list(
input = "+proj=utm +zone=40 +datum=WGS84 +units=m +no_defs",
wkt = "PROJCRS["unknown",n BASEGEOGCRS["unknown",n DATUM["World Geodetic System 1984",n ELLIPSOID["WGS 84",6378137,298.257223563,n LENGTHUNIT["metre",1]],n ID["EPSG",6326]],n PRIMEM["Greenwich",0,n ANGLEUNIT["degree",0.0174532925199433],n ID["EPSG",8901]]],n CONVERSION["UTM zone 40N",n METHOD["Transverse Mercator",n ID["EPSG",9807]],n PARAMETER["Latitude of natural origin",0,n ANGLEUNIT["degree",0.0174532925199433],n ID["EPSG",8801]],n PARAMETER["Longitude of natural origin",57,n ANGLEUNIT["degree",0.0174532925199433],n ID["EPSG",8802]],n PARAMETER["Scale factor at natural origin",0.9996,n SCALEUNIT["unity",1],n ID["EPSG",8805]],n PARAMETER["False easting",500000,n LENGTHUNIT["metre",1],n ID["EPSG",8806]],n PARAMETER["False northing",0,n LENGTHUNIT["metre",1],n ID["EPSG",8807]],n ID["EPSG",16040]],n CS[Cartesian,2],n AXIS["(E)",east,n ORDER[1],n LENGTHUNIT["metre",1,n ID["EPSG",9001]]],n AXIS["(N)",north,n ORDER[2],n LENGTHUNIT["metre",1,n ID["EPSG",9001]]]]"), class = "crs"), n_empty = 0L)), sf_column = "geometry", agr = structure(c(code = NA_integer_), levels = c("constant",
"aggregate", "identity"), class = "factor"), row.names = c(NA,
6L), class = c("sf", "data.frame"))
4
One issue you have is that the CRS for Google Maps data is WGS84 Pseudo-Mercator/EPSG:3857, and you have assigned your data WGS84/EPSG:4327.
However, somewhere in your workflow it appears you have assigned the wrong CRS to your sf/sp objects. In your dput()
examples, the coordinate values are most likely WGS84/EPSG:4326 (decimal degrees), but their CRS are WGS84 UTM Zone 40/EPSG:32640 (metres). This can happen when data are incorrectly assumed to have a certain CRS.
The result is your decimal degree values are being interpreted literally as metres within the projected bounds of EPSG:32640:
xmin = 166021.4, ymax = 9329005, ymin = 0, xmax = 833978.6
where 0 is the equator. This is why, relative to their incorrectly assigned CRS, your coordinates end up ~5m south of the equator (which incidentally is outside the permitted boundary for EPSG:32640).
It’s not possible to say definitively where the incorrect CRS was assigned without access to your full workflow, but working from your example data, here is how to correct your sf/sp objects. I couldn’t access my Google account for some reason, so I have used a shapefile downloaded from Marine Regions as a proxy for chagos_map.
library(sf)
library(sp)
library(dplyr)
library(ggplot2)
# Your example data
SP_cat2 <- new("SpatialPointsDataFrame", data = structure(list(code = c("12964",
"12964", "12964", "12964", "12964", "12964")), row.names = c(NA,
6L), class = "data.frame"), coords.nrs = 2:3, coords = structure(c(71.9684,
71.9684, 71.9684, 71.9684, 71.9684, 71.9698, -5.2417, -5.2417,
-5.2417, -5.2417, -5.2417, -5.3302), dim = c(6L, 2L), dimnames = list(
NULL, c("receiver_lon", "receiver_lat"))), bbox = structure(c(71.9684,
-5.3302, 71.9698, -5.2417), dim = c(2L, 2L), dimnames = list(
c("receiver_lon", "receiver_lat"), c("min", "max"))), proj4string = new("CRS",
projargs = "+proj=utm +zone=40 +datum=WGS84 +units=m +no_defs"))
SP32map_cat2MCP <- structure(list(code = c("12964", "12964", "12964", "12964", "12964",
"12964"), geometry = structure(list(structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9684, -5.2417
), class = c("XY", "POINT", "sfg")), structure(c(71.9698, -5.3302
), class = c("XY", "POINT", "sfg"))), class = c("sfc_POINT",
"sfc"), precision = 0, bbox = structure(c(xmin = 71.9684, ymin = -5.3302,
xmax = 71.9698, ymax = -5.2417), class = "bbox"), crs = structure(list(
input = "+proj=utm +zone=40 +datum=WGS84 +units=m +no_defs",
wkt = "PROJCRS["unknown",n BASEGEOGCRS["unknown",n DATUM["World Geodetic System 1984",n ELLIPSOID["WGS 84",6378137,298.257223563,n LENGTHUNIT["metre",1]],n ID["EPSG",6326]],n PRIMEM["Greenwich",0,n ANGLEUNIT["degree",0.0174532925199433],n ID["EPSG",8901]]],n CONVERSION["UTM zone 40N",n METHOD["Transverse Mercator",n ID["EPSG",9807]],n PARAMETER["Latitude of natural origin",0,n ANGLEUNIT["degree",0.0174532925199433],n ID["EPSG",8801]],n PARAMETER["Longitude of natural origin",57,n ANGLEUNIT["degree",0.0174532925199433],n ID["EPSG",8802]],n PARAMETER["Scale factor at natural origin",0.9996,n SCALEUNIT["unity",1],n ID["EPSG",8805]],n PARAMETER["False easting",500000,n LENGTHUNIT["metre",1],n ID["EPSG",8806]],n PARAMETER["False northing",0,n LENGTHUNIT["metre",1],n ID["EPSG",8807]],n ID["EPSG",16040]],n CS[Cartesian,2],n AXIS["(E)",east,n ORDER[1],n LENGTHUNIT["metre",1,n ID["EPSG",9001]]],n AXIS["(N)",north,n ORDER[2],n LENGTHUNIT["metre",1,n ID["EPSG",9001]]]]"), class = "crs"), n_empty = 0L)), sf_column = "geometry", agr = structure(c(code = NA_integer_), levels = c("constant",
"aggregate", "identity"), class = "factor"), row.names = c(NA,
6L), class = c("sf", "data.frame"))
# Correct current CRS and project to correct Google Maps CRS (you can safely ignore warning)
SFmap_cat2 <- st_as_sf(SP_cat2) %>%
st_set_crs(4326) %>%
st_transform(3857)
SFmap_cat2MCP <- st_set_crs(SP32map_cat2MCP, 4326) %>%
st_transform(3857)
# Creat proxy for your chagos_map object. Shapefile option downloaded from
# https://www.marineregions.org/gazetteer.php?p=details&id=62604&from=rss
# and unzipped in working directory
chagos_map <- st_read("eez_iho.shp")
chagos_map <- data.frame(st_coordinates(chagos_map)) %>%
st_as_sf(coords = c("X", "Y"), crs = 4326) %>%
summarise(geometry = st_combine(geometry), .by = L1) %>%
st_cast("POLYGON") %>%
filter(L1 != 1) %>%
st_transform(3857)
ggplot() +
geom_sf(data = chagos_map) +
geom_sf(data = SFmap_cat2,
aes(colour = as.factor(code)),
size = 2,
inherit.aes = FALSE) +
geom_sf(data = SFmap_cat2MCP,
fill = NA,
colour = "black",
alpha = 0.5,
inherit.aes = FALSE) +
scale_x_continuous(breaks = seq(7930000, 8071000, length.out = 3)) +
coord_sf(datum = 3857) +
labs(title = "MCP Silvertip Cat 2 Chagos",
colour = "Code") +
theme_minimal()