wellknown - convert WKT to GeoJSON and vice versa.

Inspiration partly comes from Python's geomet/geomet - and the name from Javascript's wellknown (it's a good name).

Different interfaces

WKT from R stuctures

There's a family of functions that make it easy to go from familiar R objects like lists and data.frames to WKT, including:

The above currently accept (depending on the fxn) numeric, list, and data.frame (and character for special case of EMPTY WKT objects).

Geojson to WKT and vice versa

geojson2wkt() and wkt2geojson() cover a subset of the various formats available:

Geojson to WKT

geojson2wkt() converts any geojson as a list to a WKT string (the same format )

WKT to Geojson

wkt2geojson() converts any WKT string into geojson as a list. This list format for geojson can be used downstream e.g., in the leaflet package.

WKT to WKB, and vice versa

wkt_wkb() converts WKT to WKB, while wkb_wkt() converts WKB to WKT

Install

Stable version

install.packages("wellknown")

Dev version

remotes::install_github("ropensci/wellknown")
# OR
install.packages("wellknown", repos="https://dev.ropensci.org")
library("wellknown")

GeoJSON to WKT

Point

point <- list(Point = c(116.4, 45.2, 11.1))
geojson2wkt(point)
#> [1] "POINT Z(116.4000000000000057  45.2000000000000028  11.0999999999999996)"

Multipoint

mp <- list(
  MultiPoint = matrix(c(100, 101, 3.14, 3.101, 2.1, 2.18), 
    ncol = 2)
)
geojson2wkt(mp)
#> [1] "MULTIPOINT ((100.0000000000000000 3.1010000000000000), (101.0000000000000000 2.1000000000000001), (3.1400000000000001 2.1800000000000002))"

LineString

st <- list(
  LineString = matrix(c(0.0, 2.0, 4.0, 5.0,
                         0.0, 1.0, 2.0, 4.0), ncol = 2)
)
geojson2wkt(st, fmt=0)
#> [1] "LINESTRING (0 0, 2 1, 4 2, 5 4)"

Multilinestring

multist <- list(
  MultiLineString = list(
   matrix(c(0, -2, -4, -1, -3, -5), ncol = 2),
   matrix(c(1.66, 10.9999, 10.9, 0, -31.5, 3.0, 1.1, 0), ncol = 2)
 )
)
geojson2wkt(multist)
#> [1] "MULTILINESTRING ((0.0000000000000000 -1.0000000000000000, -2.0000000000000000 -3.0000000000000000, -4.0000000000000000 -5.0000000000000000), (1.6599999999999999 -31.5000000000000000, 10.9999000000000002 3.0000000000000000, 10.9000000000000004 1.1000000000000001, 0.0000000000000000 0.0000000000000000))"

Polygon

poly <- list(
  Polygon = list(
    matrix(c(100.001, 101.1, 101.001, 100.001, 0.001, 0.001, 1.001, 0.001),
      ncol = 2),
    matrix(c(100.201, 100.801, 100.801, 100.201, 0.201, 0.201, 0.801, 0.201),
      ncol = 2)
  )
)
geojson2wkt(poly)
#> [1] "POLYGON ((100.0010000000000048 0.0010000000000000, 101.0999999999999943 0.0010000000000000, 101.0010000000000048 1.0009999999999999, 100.0010000000000048 0.0010000000000000), (100.2009999999999934 0.2010000000000000, 100.8010000000000019 0.2010000000000000, 100.8010000000000019 0.8010000000000000, 100.2009999999999934 0.2010000000000000))"

Multipolygon

mpoly <- list(
  MultiPolygon = list(
    list(
      matrix(c(100, 101, 101, 100, 0.001, 0.001, 1.001, 0.001), ncol = 2),
      matrix(c(100.2, 100.8, 100.8, 100.2, 0.2, 0.2, 0.8, 0.2), ncol = 2)
    ),
    list(
      matrix(c(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 1.0), ncol = 3),
      matrix(c(9.0, 10.0, 11.0, 12.0, 1.0, 2.0, 3.0, 4.0, 9.0), ncol = 3)
    )
  )
)
geojson2wkt(mpoly, fmt=1)
#> [1] "MULTIPOLYGON Z(((100.000 0.001 0.000, 101.000 0.001 0.000, 101.000 1.001 0.000, 100.000 0.001 0.000), (100.2 0.2 0.0, 100.8 0.2 0.0, 100.8 0.8 0.0, 100.2 0.2 0.0)), ((1.0 4.0 7.0, 2.0 5.0 8.0, 3.0 6.0 1.0), (9.0 12.0 3.0, 10.0 1.0 4.0, 11.0 2.0 9.0)))"

GeometryCollection

gmcoll <- list(
 GeometryCollection = list(
   list(type = 'Point', coordinates = c(0.0, 1.0)),
   list(type = 'LineString', coordinates = matrix(c(0.0, 2.0, 4.0, 5.0,
                           0.0, 1.0, 2.0, 4.0),
                           ncol = 2)),
   list(type = 'Polygon', coordinates = list(
     matrix(c(100.001, 101.1, 101.001, 100.001, 0.001, 0.001, 1.001, 0.001),
       ncol = 2),
     matrix(c(100.201, 100.801, 100.801, 100.201, 0.201, 0.201, 0.801, 0.201),
       ncol = 2)
  ))
 )
)
geojson2wkt(gmcoll, fmt=0)
#> [1] "GEOMETRYCOLLECTION (POINT (0 1), LINESTRING (0 0, 2 1, 4 2, 5 4), POLYGON ((100.001 0.001, 101.100 0.001, 101.001 1.001, 100.001 0.001), (100.201 0.201, 100.801 0.201, 100.801 0.801, 100.201 0.201)))"

Convert json or character objects

You can convert directly from an object of class json, which is output from jsonlite::toJSON().

library("jsonlite")
(json <- toJSON(list(Point = c(-105, 39)), auto_unbox = TRUE))
#> {"Point":[-105,39]}
geojson2wkt(json)
#> [1] "POINT (-105   39)"

And you can convert from a geojson character string:

str <- '{"type":"LineString","coordinates":[[0,0,10],[2,1,20],[4,2,30],[5,4,40]]}'
geojson2wkt(str)
#> [1] "LINESTRING Z(0 0 10, 2 1 20, 4 2 30, 5 4 40)"

WKT to GeoJSON

Point

As a Feature

str <- "POINT (-116.4000000000000057 45.2000000000000028)"
wkt2geojson(str)
#> $type
#> [1] "Feature"
#> 
#> $geometry
#> $geometry$type
#> [1] "Point"
#> 
#> $geometry$coordinates
#> [1] -116.4   45.2
#> 
...

Not Feature

wkt2geojson(str, feature=FALSE)
#> $type
#> [1] "Point"
#> 
#> $coordinates
#> [1] -116.4   45.2
#> 
#> attr(,"class")
#> [1] "geojson"

Multipoint

str <- 'MULTIPOINT ((100.000 3.101), (101.000 2.100), (3.140 2.180))'
wkt2geojson(str, feature=FALSE)
#> $type
#> [1] "MultiPoint"
#> 
#> $coordinates
#>        [,1]  [,2]
#> [1,] 100.00 3.101
#> [2,] 101.00 2.100
#> [3,]   3.14 2.180
#> 
#> attr(,"class")
...

Polygon

str <- "POLYGON ((100 0.1, 101.1 0.3, 101 0.5, 100 0.1), (103.2 0.2, 104.8 0.2, 100.8 0.8, 103.2 0.2))"
wkt2geojson(str, feature=FALSE)
#> $type
#> [1] "Polygon"
#> 
#> $coordinates
#> $coordinates[[1]]
#>       [,1] [,2]
#> [1,] 100.0  0.1
#> [2,] 101.1  0.3
#> [3,] 101.0  0.5
#> [4,] 100.0  0.1
...

MultiPolygon

str <- "MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),
    ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20)))"
wkt2geojson(str, feature=FALSE)
#> $type
#> [1] "MultiPolygon"
#> 
#> $coordinates
#> $coordinates[[1]]
#> $coordinates[[1]][[1]]
#>      [,1] [,2]
#> [1,]   40   40
#> [2,]   20   45
#> [3,]   45   30
...

Linestring

wkt2geojson("LINESTRING (0 -1, -2 -3, -4 5)", feature=FALSE)
#> $type
#> [1] "LineString"
#> 
#> $coordinates
#>      [,1] [,2]
#> [1,]    0   -1
#> [2,]   -2   -3
#> [3,]   -4    5
#> 
#> attr(,"class")
...

lint WKT

lint("POINT (1 2)")
#> [1] TRUE
lint("LINESTRING EMPTY")
#> [1] TRUE
lint("MULTIPOINT ((1 2), (3 4), (-10 100))")
#> [1] TRUE
lint("POLYGON((20.3 28.6, 20.3 19.6, 8.5 19.6, 8.5 28.6, 20.3 28.6))")
#> [1] TRUE
lint("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))")
#> [1] TRUE
lint("POINT (1 2 3 4 5)")
#> [1] FALSE
lint("LINESTRING (100)")
#> [1] FALSE
lint("MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, a b, 10 20, 5 10, 15 5)))")
#> [1] FALSE

WKT <--> WKB

WKT to WKB

## point
wkt_wkb("POINT (-116.4 45.2)")
#>  [1] 01 01 00 00 00 9a 99 99 99 99 19 5d c0 9a 99 99 99 99 99 46 40

## polygon
wkt_wkb("POLYGON ((100.0 0.0, 101.1 0.0, 101.0 1.0, 100.0 0.0))")
#>  [1] 01 03 00 00 00 01 00 00 00 04 00 00 00 00 00 00 00 00 00 59 40 00 00 00 00
#> [26] 00 00 00 00 66 66 66 66 66 46 59 40 00 00 00 00 00 00 00 00 00 00 00 00 00
#> [51] 40 59 40 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 59 40 00 00 00 00 00 00
#> [76] 00 00

WKB to WKT

## point
(x <- wkt_wkb("POINT (-116.4 45.2)"))
#>  [1] 01 01 00 00 00 9a 99 99 99 99 19 5d c0 9a 99 99 99 99 99 46 40
wkb_wkt(x)
#> [1] "POINT (-116.4 45.2)"

## polygon
(x <- wkt_wkb("POLYGON ((100.0 0.0, 101.1 0.0, 101.0 1.0, 100.0 0.0))"))
#>  [1] 01 03 00 00 00 01 00 00 00 04 00 00 00 00 00 00 00 00 00 59 40 00 00 00 00
#> [26] 00 00 00 00 66 66 66 66 66 46 59 40 00 00 00 00 00 00 00 00 00 00 00 00 00
#> [51] 40 59 40 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 59 40 00 00 00 00 00 00
#> [76] 00 00
wkb_wkt(x)
#> [1] "POLYGON ((100 0, 101.1 0, 101 1, 100 0))"

Bounding boxes

A bounding box is a very simple concept: a representation of the smallest area in which all the points in a dataset lie. In WKT, bounding boxes look like:

POLYGON((10 14,10 16,12 16,12 14,10 14))

Sometimes you've got WKT data like this - a Polygon, a LineString, whatever - and you want a bounding box in a format R can understand. The answer is wkt_bounding, which takes a vector of valid WKT objects and produces a data.frame or matrix of R representations, whichever you'd prefer:

wkt <- c("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))",
         "LINESTRING (30 10, 10 90, 40 40)")
wkt_bounding(wkt)
#>   min_x min_y max_x max_y
#> 1    10    10    40    40
#> 2    10    10    40    90

Alternately you might want to go in the other direction and turn R bounding boxes into WKT objects. You can do that with, appropriately, bounding_wkt:

bounding_wkt(min_x = 10, min_y = 10, max_x = 40, max_y = 40)
#> [1] "POLYGON((10 10,10 40,40 40,40 10,10 10))"

This accepts either a series of vectors, one for each min or max value, or a list of length-4 vectors. Either way, it produces a nice WKT representation of the R data you give it.

WKT validation

validate_wkt takes a vector of WKT objects and spits out a data.frame containing whether each object is valid, and any comments the parser has in the case that it isn't:

wkt <- c("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))",
         "ARGHLEFLARFDFG",
         "LINESTRING (30 10, 10 90, 40 some string)")
validate_wkt(wkt)
#>   is_valid
#> 1    FALSE
#> 2    FALSE
#> 3    FALSE
#>                                                                                                                          comments
#> 1                                           The WKT object has a different orientation from the default. Use ?wkt_correct to fix.
#> 2                                                                          Object could not be recognised as a supported WKT type
#> 3 bad lexical cast: source type value could not be interpreted as target at 'some' in 'linestring (30 10, 10 90, 40 some string)'

With this you can check and clean your data before you rely on it and watch all your code fall down in a heap.

Coordinate and centroid extraction

WKT POLYGONs are often used to store latitude and longitude coordinates - and you can use wkt_coords to get them:

wkt_coords(("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"))
#>   object  ring lng lat
#> 1      1 outer  30  10
#> 2      1 outer  40  40
#> 3      1 outer  20  40
#> 4      1 outer  10  20
#> 5      1 outer  30  10

The result of a wkt_coords call is a data.frame of four columns - object, identifying which of the input WKT objects the row refers to, ring referring to the layer in that object, and then lat and lng.

Extracting centroids is also useful, and can be performed with wkt_centroid. Again, it's entirely vectorised and produces a data.frame:

wkt_centroid(("POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))"))
#>        lng     lat
#> 1 25.45455 26.9697


ropensci/wellknown documentation built on April 8, 2023, 11:54 p.m.