Skip to content

Commit

Permalink
v0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmach committed Sep 3, 2014
0 parents commit 3e8aea4
Show file tree
Hide file tree
Showing 11 changed files with 1,277 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
@@ -0,0 +1,13 @@
language: go

go:
- 1.2
- 1.3

after_script:
- FIXED=$(go vet ./... | wc -l); if [ $FIXED -gt 0 ]; then echo "go vet - $FIXED issues(s), please fix." && exit 2; fi
- FIXED=$(go fmt ./... | wc -l); if [ $FIXED -gt 0 ]; then echo "gofmt - $FIXED file(s) not formatted correctly, please run gofmt to fix this." && exit 2; fi

script:
- go test -v

18 changes: 18 additions & 0 deletions LICENSE
@@ -0,0 +1,18 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

100 changes: 100 additions & 0 deletions README.md
@@ -0,0 +1,100 @@
go.geojson
==========

go.geojson is a library for **encoding and decoding** [GeoJSON](http://geojson.org/) into Go structs.
Supports both the [json.Marshaler](http://golang.org/pkg/encoding/json/#Marshaler) and [json.Unmarshaler](http://golang.org/pkg/encoding/json/#Unmarshaler)
interfaces as well as helper functions such as `UnmarshalFeatureCollection`, `UnmarshalFeature` and `UnmarshalGeometry`.

#### To install

go get github.com/paulmach/go.geojson

#### To use, imports as package name `geojson`:

import "github.com/paulmach/go.geojson"

<br />
[![Build Status](https://travis-ci.org/paulmach/go.geojson.png?branch=master)](https://travis-ci.org/paulmach/go.geojson)
&nbsp; &nbsp;
[![Coverage Status](https://coveralls.io/repos/paulmach/go.geojson/badge.png?branch=master)](https://coveralls.io/r/paulmach/go.geojson?branch=master)
&nbsp; &nbsp;
[![Godoc Reference](https://godoc.org/github.com/paulmach/go.geojson?status.png)](https://godoc.org/github.com/paulmach/go.geojson)

## Examples

* #### Unmarshalling (JSON -> Go)

go.geojson supports both the [json.Marshaler](http://golang.org/pkg/encoding/json/#Marshaler) and [json.Unmarshaler](http://golang.org/pkg/encoding/json/#Unmarshaler) interfaces as well as helper functions such as `UnmarshalFeatureCollection`, `UnmarshalFeature` and `UnmarshalGeometry`.

// Feature Collection
rawFeatureJSON := []byte(`
{ "type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
"properties": {"prop0": "value0"}
}
]
}`)

fc1, err := geojson.UnmarshalFeatureCollection(rawFeatureJSON)

fc2 := geojson.NewFeatureCollection()
err := json.Unmarshal(rawJSON, fc2)

// Geometry
rawGeometryJSON := []byte(`{"type": "Point", "coordinates": [102.0, 0.5]}`)
g, err := geojson.UnmarshalGeometry(rawGeometryJSON)

g.IsPoint() == true
g.Point == []float64{102.0, 0.5}


* #### Marshalling (Go -> JSON)

g := geojson.NewPointGeometry([]float64{1, 2})
rawJSON, err := g.MarshalJSON()

fc := geojson.NewFeatureCollection()
fc.AddFeature(geojson.NewPointFeature([]float64{1,2}))
rawJSON, err := fc.MarshalJSON()

* #### Dealing with different Geometry types

A geometry can be of several types, causing problems in a statically typed language.
Thus there is a separate attribute on Geometry for each type.
See the [Geometry object](https://godoc.org/github.com/paulmach/go.geojson#Geometry) for more details.

g := UnmarshalGeometry([]byte(`
{
"type": "LineString",
"coordinates": [
[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
]
}`))

switch {
case g.IsPoint():
// do something with g.Point
case g.IsLineString():
// do something with g.LineString
}

## Feature Properties

GeoJSON [Features](http://geojson.org/geojson-spec.html#feature-objects) can have properties of any type
causing issues in a statically typed language such as Go.
So, included are some helper methods on the Feature object to make the ease the pain.

// functions to do the casting for you
func (f Feature) PropertyBool(key string) (bool, error) {
func (f Feature) PropertyInt(key string) (int, error) {
func (f Feature) PropertyFloat64(key string) (float64, error) {
func (f Feature) PropertyString(key string) (string, error) {

// functions that hide the error and let you define default
func (f Feature) PropertyMustBool(key string, def ...bool) bool {
func (f Feature) PropertyMustInt(key string, def ...int) int {
func (f Feature) PropertyMustFloat64(key string, def ...float64) float64 {
func (f Feature) PropertyMustString(key string, def ...string) string {

83 changes: 83 additions & 0 deletions feature.go
@@ -0,0 +1,83 @@
package geojson

import (
"encoding/json"
)

// A Feature corresponds to GeoJSON feature object
type Feature struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
BoundingBox []float64 `json:"bbox,omitempty"`
Geometry *Geometry `json:"geometry"`
Properties map[string]interface{} `json:"properties"`
CRS map[string]interface{} `json:"crs,omitempty"` // Coordinate Reference System Objects are not currently supported
}

// NewFeature creates and initializes a GeoJSON feature given the required attributes.
func NewFeature(geometry *Geometry) *Feature {
return &Feature{
Type: "Feature",
Geometry: geometry,
Properties: make(map[string]interface{}),
}
}

// NewPointFeature creates and initializes a GeoJSON feature with a point geometry using the given coordinate.
func NewPointFeature(coordinate []float64) *Feature {
return NewFeature(NewPointGeometry(coordinate))
}

// NewMultiPointFeature creates and initializes a GeoJSON feature with a multi-point geometry using the given coordinates.
func NewMultiPointFeature(coordinates ...[]float64) *Feature {
return NewFeature(NewMultiPointGeometry(coordinates...))
}

// NewLineStringFeature creates and initializes a GeoJSON feature with a line string geometry using the given coordinates.
func NewLineStringFeature(coordinates [][]float64) *Feature {
return NewFeature(NewLineStringGeometry(coordinates))
}

// NewMultiLineStringFeature creates and initializes a GeoJSON feature with a multi-line string geometry using the given lines.
func NewMultiLineStringFeature(lines ...[][]float64) *Feature {
return NewFeature(NewMultiLineStringGeometry(lines...))
}

// NewPolygonFeature creates and initializes a GeoJSON feature with a polygon geometry using the given polygon.
func NewPolygonFeature(polygon [][][]float64) *Feature {
return NewFeature(NewPolygonGeometry(polygon))
}

// NewMultiPolygonFeature creates and initializes a GeoJSON feature with a multi-polygon geometry using the given polygons.
func NewMultiPolygonFeature(polygons ...[][][]float64) *Feature {
return NewFeature(NewMultiPolygonGeometry(polygons...))
}

// NewCollectionFeature creates and initializes a GeoJSON feature with a geometry collection geometry using the given geometries.
func NewCollectionFeature(geometries ...*Geometry) *Feature {
return NewFeature(NewCollectionGeometry(geometries...))
}

// MarshalJSON converts the feature object into the proper JSON.
// It will handle the encoding of all the child geometries.
// Alternately one can call json.Marshal(f) directly for the same result.
func (f *Feature) MarshalJSON() ([]byte, error) {
f.Type = "Feature"
if len(f.Properties) == 0 {
f.Properties = nil
}

return json.Marshal(*f)
}

// UnmarshalFeature decodes the data into a GeoJSON feature.
// Alternately one can call json.Unmarshal(f) directly for the same result.
func UnmarshalFeature(data []byte) (*Feature, error) {
f := &Feature{}
err := json.Unmarshal(data, f)
if err != nil {
return nil, err
}

return f, nil
}
55 changes: 55 additions & 0 deletions feature_collection.go
@@ -0,0 +1,55 @@
/*
Package go.geojson is a library for encoding and decoding GeoJSON into Go structs.
Supports both the json.Marshaler and json.Unmarshaler interfaces as well as helper functions
such as `UnmarshalFeatureCollection`, `UnmarshalFeature` and `UnmarshalGeometry`.
*/
package geojson

import (
"encoding/json"
)

// A FeatureCollection correlates to a GeoJSON feature collection.
type FeatureCollection struct {
Type string `json:"type"`
BoundingBox []float64 `json:"bbox,omitempty"`
Features []*Feature `json:"features"`
CRS map[string]interface{} `json:"crs,omitempty"` // Coordinate Reference System Objects are not currently supported
}

// NewFeatureCollection creates and initializes a new feature collection.
func NewFeatureCollection() *FeatureCollection {
return &FeatureCollection{
Type: "FeatureCollection",
Features: make([]*Feature, 0),
}
}

// AddFeature appends a feature to the collection.
func (fc *FeatureCollection) AddFeature(feature *Feature) *FeatureCollection {
fc.Features = append(fc.Features, feature)
return fc
}

// MarshalJSON converts the feature collection object into the proper JSON.
// It will handle the encoding of all the child features and geometries.
// Alternately one can call json.Marshal(fc) directly for the same result.
func (fc *FeatureCollection) MarshalJSON() ([]byte, error) {
fc.Type = "FeatureCollection"
if fc.Features == nil {
fc.Features = make([]*Feature, 0) // GeoJSON requires the feature attribute to be at least []
}
return json.Marshal(*fc)
}

// UnmarshalFeatureCollection decodes the data into a GeoJSON feature collection.
// Alternately one can call json.Unmarshal(fc) directly for the same result.
func UnmarshalFeatureCollection(data []byte) (*FeatureCollection, error) {
fc := &FeatureCollection{}
err := json.Unmarshal(data, fc)
if err != nil {
return nil, err
}

return fc, nil
}
77 changes: 77 additions & 0 deletions feature_collection_test.go
@@ -0,0 +1,77 @@
package geojson

import (
"bytes"
"testing"
)

func TestNewFeatureCollection(t *testing.T) {
fc := NewFeatureCollection()

if fc.Type != "FeatureCollection" {
t.Errorf("should have type of FeatureCollection, got %v", fc.Type)
}
}

func TestUnmarshalFeatureCollection(t *testing.T) {
rawJSON := `
{ "type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
"properties": {"prop0": "value0"}
},
{ "type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
]
},
"properties": {
"prop0": "value0",
"prop1": 0.0
}
},
{ "type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
[100.0, 1.0], [100.0, 0.0] ]
]
},
"properties": {
"prop0": "value0",
"prop1": {"this": "that"}
}
}
]
}`

fc, err := UnmarshalFeatureCollection([]byte(rawJSON))
if err != nil {
t.Fatalf("should unmarshal feature collection without issue, err %v", err)
}

if fc.Type != "FeatureCollection" {
t.Errorf("should have type of FeatureCollection, got %v", fc.Type)
}

if len(fc.Features) != 3 {
t.Errorf("should have 3 features but got %d", len(fc.Features))
}
}

func TestFeatureCollectionMarshalJSON(t *testing.T) {
fc := NewFeatureCollection()
blob, err := fc.MarshalJSON()

if err != nil {
t.Fatalf("should marshal to json just fine but got %v", err)
}

if !bytes.Contains(blob, []byte(`"features":[]`)) {
t.Errorf("json should set features object to at least empty array")
}
}

0 comments on commit 3e8aea4

Please sign in to comment.