Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: geocrystal/geo
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.7.4
Choose a base ref
...
head repository: geocrystal/geo
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 16 commits
  • 10 files changed
  • 2 contributors

Commits on Apr 11, 2024

  1. Calculates the location of a destination coord

    mamantoha committed Apr 11, 2024
    Copy the full SHA
    a679024 View commit details
  2. Merge pull request #12 from geocrystal/destination

    Calculates the location of a destination coord
    mamantoha authored Apr 11, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    888267b View commit details
  3. v0.7.5

    mamantoha committed Apr 11, 2024
    Copy the full SHA
    4fdd17c View commit details
  4. add DISTANCE_UNITS

    mamantoha committed Apr 11, 2024
    Copy the full SHA
    c9f4ae9 View commit details
  5. use geocrystal/ring_area for area

    mamantoha committed Apr 11, 2024
    Copy the full SHA
    36d788a View commit details
  6. Merge pull request #13 from geocrystal/ring_area

    use geocrystal/ring_area for area
    mamantoha authored Apr 11, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    caa2a85 View commit details
  7. add geocrystal/geojson and to_geojson methods

    mamantoha committed Apr 11, 2024
    Copy the full SHA
    0ea4fd7 View commit details
  8. Merge pull request #14 from geocrystal/geojson

    add geocrystal/geojson and to_geojson methods
    mamantoha authored Apr 11, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    663986e View commit details

Commits on Apr 30, 2024

  1. bump ring_area

    mamantoha committed Apr 30, 2024
    Copy the full SHA
    e687d7a View commit details
  2. v0.7.6

    mamantoha committed Apr 30, 2024
    Copy the full SHA
    532852c View commit details
  3. update area

    mamantoha committed Apr 30, 2024
    Copy the full SHA
    316468e View commit details

Commits on Dec 31, 2024

  1. Copy the full SHA
    5d8d654 View commit details
  2. Add WKT

    jgaskins committed Dec 31, 2024
    Copy the full SHA
    f89ad2e View commit details
  3. Add WKB

    jgaskins committed Dec 31, 2024
    Copy the full SHA
    4fc2de6 View commit details
  4. Add Polygon#to_wkt

    jgaskins committed Dec 31, 2024
    Copy the full SHA
    2e6ed8e View commit details

Commits on Jan 6, 2025

  1. Merge pull request #15 from jgaskins/add-ewkt-and-ewkb

    Add serialization to Well Known Text/Binary formats for `Geo::Coord`
    mamantoha authored Jan 6, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    057da62 View commit details
Showing with 238 additions and 17 deletions.
  1. +13 −0 README.md
  2. +7 −2 shard.yml
  3. +59 −0 spec/geo/coord_spec.cr
  4. +7 −0 spec/geo/distance_spec.cr
  5. +42 −6 spec/geo/polygon_spec.cr
  6. +27 −0 spec/geo_spec.cr
  7. +6 −0 src/geo.cr
  8. +44 −4 src/geo/coord.cr
  9. +7 −2 src/geo/distance.cr
  10. +26 −3 src/geo/polygon.cr
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -171,6 +171,19 @@ new_york.distance(london).to_kilometers
# => 5570.4744596620685
```

### Calculates the location of a destination coord

```crystal
require "geo"
require "geo/distance"
point = Geo::Coord.new(39, -75)
point.destination(5000, 90, :kilometers)
# Geo::Coord(@lat=26.440010707631124, @lng=-22.885355549364313)
```


## Contributing

1. Fork it (<https://github.com/geocrystal/geo/fork>)
9 changes: 7 additions & 2 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
name: geo
version: 0.7.4
version: 0.7.6

crystal: ">= 1.0.0"

dependencies:
haversine:
github: geocrystal/haversine
version: ">= 0.4.0"
version: ">= 0.5.0"
convex_hull:
github: geocrystal/convex_hull
version: ">= 0.6.0"
geohash:
github: geocrystal/geohash
geo_bearing:
github: geocrystal/geo_bearing
ring_area:
github: geocrystal/ring_area
version: ">= 0.2.1"
geojson:
github: geocrystal/geojson

development_dependencies:
ameba:
59 changes: 59 additions & 0 deletions spec/geo/coord_spec.cr
Original file line number Diff line number Diff line change
@@ -103,6 +103,65 @@ describe Geo::Coord do
end
end

describe "#to_geojson" do
coord = Geo::Coord.new(-15, 125)

geojson = coord.to_geojson

geojson.should be_a(GeoJSON::Coordinates)
end

describe "#to_wkt" do
it "generates a Well Known Text format" do
coord = Geo::Coord.new(50.004444, 36.231389)

ewkt = coord.to_wkt

ewkt.should eq "POINT(36.231389 50.004444)"
end
end

describe "#to_wkb" do
it "generates a Well Known Binary format" do
coord = Geo::Coord.new(12, 34)

ewkb = coord.to_wkb

ewkb.should eq Bytes[
0, # Big-Endian
0, 0, 0, 1, # POINT
0, 0, 0, 34, 0, 0, 0, 0, # Longitude encoded as IEEE-754
0, 0, 0, 12, 0, 0, 0, 0, # Latitude encoded as IEEE-754
]
end
end

describe "#to_ewkt" do
it "generates an Extended Well Known Text format" do
coord = Geo::Coord.new(50.004444, 36.231389)

ewkt = coord.to_ewkt

ewkt.should eq "SRID=4326;POINT(36.231389 50.004444)"
end
end

describe "#to_ewkb" do
it "generates an Extended Well Known Binary format" do
coord = Geo::Coord.new(12, 34)

ewkb = coord.to_ewkb

ewkb.should eq Bytes[
0, # Big-Endian
0, 0, 0, 1, # POINT
0, 0, 0, 34, 0, 0, 0, 0, # Longitude encoded as IEEE-754
0, 0, 0, 12, 0, 0, 0, 0, # Latitude encoded as IEEE-754
16, 140, # SRID 4326
]
end
end

describe "comparisons" do
describe "equality" do
pos1 = Geo::Coord.new(45.3142533036254, -93.47527313511819)
7 changes: 7 additions & 0 deletions spec/geo/distance_spec.cr
Original file line number Diff line number Diff line change
@@ -4,11 +4,18 @@ require "../../src/geo/distance"
describe Geo::Coord do
london = Geo::Coord.new(51.500153, -0.126236)
new_york = Geo::Coord.new(40.714268, -74.005974)
point = Geo::Coord.new(39, -75)

context "distance" do
context "calculates distance (by haversine formula)" do
it { london.distance(london).to_kilometers.should eq(0) }
it { new_york.distance(london).to_kilometers.should eq(5570.482153929098) }
end
end

context "destination" do
context "Calculates the location of a destination point (by haversine formula)" do
it { point.destination(5000, 90, :kilometers).should eq(Geo::Coord.new(26.440010707631124, -22.885355549364313)) }
end
end
end
48 changes: 42 additions & 6 deletions spec/geo/polygon_spec.cr
Original file line number Diff line number Diff line change
@@ -85,15 +85,51 @@ describe Geo::Polygon do

describe "#area" do
coords = [
Geo::Coord.new(-3, -2),
Geo::Coord.new(-1, 4),
Geo::Coord.new(6, 1),
Geo::Coord.new(3, 10),
Geo::Coord.new(-4, 9),
Geo::Coord.new(-15, 125),
Geo::Coord.new(-22, 113),
Geo::Coord.new(-37, 117),
Geo::Coord.new(-33, 130),
Geo::Coord.new(-39, 148),
Geo::Coord.new(-27, 154),
Geo::Coord.new(-15, 144),
Geo::Coord.new(-15, 125),
]

polygon = Geo::Polygon.new(coords)

it { polygon.area.should be_a(RingArea::Area) }
it { polygon.area.to_meters.should eq(7748891609977.457) }
end

describe "#to_geojson" do
coords = [
Geo::Coord.new(-15, 125),
Geo::Coord.new(-22, 113),
Geo::Coord.new(-37, 117),
Geo::Coord.new(-33, 130),
Geo::Coord.new(-39, 148),
Geo::Coord.new(-27, 154),
Geo::Coord.new(-15, 144),
Geo::Coord.new(-15, 125),
]

polygon = Geo::Polygon.new(coords)
geojson = polygon.to_geojson

geojson.should be_a(GeoJSON::Polygon)
end

describe "#to_wkt" do
it "outputs a Well Known Text format" do
polygon = Geo::Polygon.new([
Geo::Coord.new(10, 30),
Geo::Coord.new(20, 10),
Geo::Coord.new(40, 20),
Geo::Coord.new(40, 40),
])

it { polygon.area.should eq(60.0) }
polygon.to_wkt.should eq "POLYGON((30 10, 10 20, 20 40, 40 40, 30 10))"
end
end

describe "comparisons" do
27 changes: 27 additions & 0 deletions spec/geo_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require "./spec_helper"

describe Geo do
context "DISTANCE_UNITS" do
it do
distance_units = {
:centimeters,
:centimetres,
:degrees,
:feet,
:inches,
:kilometers,
:kilometres,
:meters,
:metres,
:miles,
:millimeters,
:millimetres,
:nautical_miles,
:radians,
:yards,
}

Geo::DISTANCE_UNITS.should eq(distance_units)
end
end
end
6 changes: 6 additions & 0 deletions src/geo.cr
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
require "convex_hull"
require "geohash"
require "geo_bearing"
require "haversine"
require "ring_area"
require "geojson"
require "./geo/utils"
require "./geo/coord"
require "./geo/polygon"
require "./geo/distance"

module Geo
VERSION = {{ `shards version #{__DIR__}`.chomp.stringify }}

DISTANCE_UNITS = Haversine::FACTORS.keys
end
48 changes: 44 additions & 4 deletions src/geo/coord.cr
Original file line number Diff line number Diff line change
@@ -156,6 +156,10 @@ module Geo
Geohash.encode(lat.to_f, lng.to_f, precision)
end

def to_geojson : GeoJSON::Coordinates
GeoJSON::Coordinates.new(lng.to_f64, lat.to_f64)
end

# Returns a string representing coordinates.
#
# ```
@@ -166,6 +170,46 @@ module Geo
io << strfcoord(%{%latd°%latm'%lats"%lath %lngd°%lngm'%lngs"%lngh})
end

def to_ewkt : String
String.build { |str| to_ewkt str }
end

def to_ewkt(io : IO, output_type = true, output_parentheses = true) : Nil
# SRID 4326 is used for latitude and longitude
# https://epsg.org/crs_4326/WGS-84.html
io << "SRID=4326;"
to_wkt io, output_type: output_type, output_parentheses: output_parentheses
end

def to_ewkb(bytes : Bytes = Bytes.new(23), byte_format : IO::ByteFormat = IO::ByteFormat::BigEndian) : Bytes
to_wkb bytes, byte_format
# SRID 4326 is used for latitude and longitude
# https://epsg.org/crs_4326/WGS-84.html
byte_format.encode 4236i16, bytes + 21

bytes
end

def to_wkt : String
String.build { |str| to_wkt str }
end

def to_wkt(io : IO, output_type = true, output_parentheses = true) : Nil
io << "POINT" if output_type
io << '(' if output_parentheses
io << lng << ' ' << lat
io << ')' if output_parentheses
end

def to_wkb(bytes : Bytes = Bytes.new(21), byte_format : IO::ByteFormat = IO::ByteFormat::BigEndian) : Bytes
bytes[0] = 0 # Big Endian
byte_format.encode 1u32, bytes + 1 # POINT type
byte_format.encode lng, bytes + 5
byte_format.encode lat, bytes + 13

bytes
end

def ll
{lat, lng}
end
@@ -188,10 +232,6 @@ module Geo
true
end

def shoelace(other : Geo::Coord)
lat * other.lng - lng * other.lat
end

private def guard_seconds(pattern : String, result : String) : Array(String)?
if m = pattern.match(/<(lat|lng)s>/)
return [result, ""] unless m && result.starts_with?("60")
9 changes: 7 additions & 2 deletions src/geo/distance.cr
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
require "haversine"

module Geo
struct Coord
# Calculates distance to `other`.
# Haversine formula is used.
def distance(other : Geo::Coord) : Haversine::Distance
Haversine.distance(self.ll, other.ll)
end

# Calculates the location of a destination point
def destination(distance : Number, bearing : Number, unit : Symbol = :kilometers) : Geo::Coord
point = Haversine.destination(self.ll, distance, bearing, unit)

Geo::Coord.new(point[0], point[1])
end
end
end
29 changes: 26 additions & 3 deletions src/geo/polygon.cr
Original file line number Diff line number Diff line change
@@ -25,9 +25,11 @@ module Geo
@coords
end

# Evaluate area of a polygon using shoelace formula
def area
@coords.each_cons_pair.sum { |lat, lng| lat.shoelace(lng) }.abs.fdiv(2)
# Return the approximate signed geodesic area of the polygon.
def area : RingArea::Area
coordinates = @coords.map { |coord| [coord.lng, coord.lat] }

RingArea.ring_area(coordinates)
end

# Order coords in lexicographical order.
@@ -98,6 +100,27 @@ module Geo
size == other.size
end

def to_geojson : GeoJSON::Polygon
coordinates = @coords.map { |coord| GeoJSON::Coordinates.new(coord.lng.to_f64, coord.lat.to_f64) }

GeoJSON::Polygon.new([coordinates])
end

def to_wkt : String
String.build { |str| to_wkt str }
end

def to_wkt(io : IO) : Nil
io << "POLYGON(("
@coords.each_with_index 1 do |coord, index|
coord.to_wkt io, output_type: false, output_parentheses: false
if index < @coords.size
io << ", "
end
end
io << "))"
end

private def calculate_centroid : Geo::Coord
centroid_lat = 0.0
centroid_lng = 0.0