Include types from gonavitia/navitia
This commit is contained in:
parent
82769f923e
commit
24b1102761
157 changed files with 26062 additions and 116 deletions
4
types/.gitignore
vendored
Normal file
4
types/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
*.zip
|
||||
Fuzz*Dir/
|
||||
*.prof
|
||||
*.test
|
||||
78
types/README.md
Normal file
78
types/README.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# navitia/types is a library for working with types returned by the [Navitia](navitia.io) API. [](https://godoc.org/github.com/govitia/navitia/types)
|
||||
|
||||
Package types implements support for the types used in the Navitia API (see doc.navitia.io), simplified and modified for idiomatic Go use.
|
||||
|
||||
This is navitia/types v0.2. It is not API-Stable, and won't be until the v1 release of navitia, but it's getting closer !
|
||||
This package was and is developped as a supporting library for the [navitia API client](https://github.com/govitia/navitia) but can be used to build other navitia API clients.
|
||||
|
||||
## Install
|
||||
|
||||
Simply run `go get -u github.com/govitia/navitia/types`.
|
||||
|
||||
## Coverage
|
||||
|
||||
Preview of the supported types, see [the doc](https://godoc.org/github.com/govitia/navitia-types) for more information, and the [navitia.io doc](http://doc.navitia.io) for information about the remote API.
|
||||
|
||||
|Type Name|Description|Navitia Name|
|
||||
|---|---|---|
|
||||
|[`Journey`](https://godoc.org/github.com/govitia/navitia-types#Journey)|A journey (X-->Y)|"journey"|
|
||||
|[`Section`](https://godoc.org/github.com/govitia/navitia-types#Section)|A section of a `Journey`|"section"|
|
||||
|[`Region`](https://godoc.org/github.com/govitia/navitia-types#Region)|A region covered by the API|"region"|
|
||||
|[`Isochrone`](https://godoc.org/github.com/govitia/navitia-types#Region)|A region covered by the API|"isochrone"|
|
||||
|[`Container`](https://godoc.org/github.com/govitia/navitia-types#Container)|This contains a Place or a PTObject|"place"/"pt_object"|
|
||||
|[`Place`](https://godoc.org/github.com/govitia/navitia-types#Place)|Place is an empty interface, by convention used to identify an `Address`, [`StopPoint`](https://godoc.org/github.com/govitia/navitia-types#StopPoint), [`StopArea`](https://godoc.org/github.com/govitia/navitia-types#StopArea), [`POI`](https://godoc.org/github.com/govitia/navitia-types#POI), [`Admin`](https://godoc.org/github.com/govitia/navitia-types#Admin) & [`Coordinates`](https://godoc.org/github.com/govitia/navitia-types#Coordinates).|
|
||||
|[`PTObject`](https://godoc.org/github.com/govitia/navitia-types#Place)|PTObject is an empty interface by convention used to identify a Public Transportation object|
|
||||
|[`Line`](https://godoc.org/github.com/govitia/navitia-types#Line)|A public transit line.|"line"|
|
||||
|[`Route`](https://godoc.org/github.com/govitia/navitia-types#Route)|A specific route within a `Line`.|"route"|
|
||||
|
||||
And others, such as [`Display`](https://godoc.org/github.com/govitia/navitia-types#Display) ["display_informations"], [`PTDateTime`](https://godoc.org/github.com/govitia/navitia-types#PTDateTime) ["pt-date-time"], [`StopTime`](https://godoc.org/github.com/govitia/navitia-types#StopTime) ["stop_time"]
|
||||
|
||||
## Getting started
|
||||
|
||||
```golang
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/govitia/navitia/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := []byte{"some journey's json"}
|
||||
var j types.Journey
|
||||
_ = j.UnmarshalJSON(data)
|
||||
}
|
||||
```
|
||||
|
||||
### Going further
|
||||
|
||||
Obviously, this is a very simple example of what navitia/types can do, [check out the documentation !](https://godoc.org/github.com/govitia/navitia/types)
|
||||
|
||||
## What's new in v0.2
|
||||
|
||||
- Merge back into the `navitia` tree !
|
||||
- `Container` is now a type that can be used as a Place Container or as a PTObject Container, which helps everyone!
|
||||
- No more `String` methods
|
||||
- Better unmarshalling, including better error handling, along with better testing
|
||||
- Benchmarks !
|
||||
- `Disruption` support, along with what it entails.
|
||||
- Rename `JourneyStatus` to `Effect`
|
||||
- And others ! See `git log` for more information !
|
||||
|
||||
## TODO
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update `readme.md` to reflect new changes
|
||||
- Add links to the doc.navitia.io documentation to every type
|
||||
|
||||
### Testing
|
||||
|
||||
- `(*PTDateTime).UnmarshalJSON`
|
||||
- `ErrInvalidPlaceContainer.Error`
|
||||
- `Equipment.Known`
|
||||
- Every Type should have at least one file to be tested against
|
||||
- Globalise mutual code in unmarshal testers
|
||||
|
||||
## Footnotes
|
||||
|
||||
I made this project as I wanted to explore and push my go skills, and I'm really up for you to contribute ! Send me a pull request and/or contact me if you have any questions! ( [@aabizri](https://twitter.com/aabizri) on twitter)
|
||||
6
types/activePeriod.go
Normal file
6
types/activePeriod.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package types
|
||||
|
||||
type ActivePeriod struct {
|
||||
Begin string `json:"begin"`
|
||||
End string `json:"end"`
|
||||
}
|
||||
9
types/calendar.go
Normal file
9
types/calendar.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// Calendar is returned on vehicle journey message and indicates periodicity informations
|
||||
// about transport schedules.
|
||||
type Calendar struct {
|
||||
ActivePeriods []ActivePeriod `json:"active_periods"`
|
||||
WeekPattern WeekPattern `json:"week_pattern"`
|
||||
Exceptions []Exception `json:"exceptions"`
|
||||
}
|
||||
9
types/channel.go
Normal file
9
types/channel.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// A Channel is a destination media for a message.
|
||||
type Channel struct {
|
||||
ID ID `json:"id"` // ID of the address
|
||||
ContentType string `json:"content_type"` // Content Type (text/html etc.) RFC1341.4
|
||||
Name string `json:"name"` // Name of the channel
|
||||
Types []string `json:"types,omitempty"` // Types ?
|
||||
}
|
||||
65
types/common_test.go
Normal file
65
types/common_test.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// testUnmarshal is a helper to test unmarshalling for any value implementing results.
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func testUnmarshal(t *testing.T, data typeTestData, resultsType reflect.Type) {
|
||||
t.Helper()
|
||||
// Create the run function generator, allowing us to run this in parallel
|
||||
rgen := func(data []byte, correct bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
// Declare this test to be run in parallel
|
||||
t.Parallel()
|
||||
|
||||
// Create a pointer to a new value of the type indicated in resultsType
|
||||
res := reflect.New(resultsType).Interface()
|
||||
|
||||
// We use encoding/json's unmarshaller, as we don't have one for this type
|
||||
err := json.Unmarshal(data, res)
|
||||
|
||||
// We check that the result is what we expect:
|
||||
// If we expect no errors (correct == true) but we get one, the test has failed
|
||||
// If we expect an error (correct == false) but we don't get one, the test has failes
|
||||
// In all other cases, the test is successful !
|
||||
if err != nil && correct {
|
||||
t.Errorf("expected no errors but got one: %v", err)
|
||||
} else if err == nil && !correct {
|
||||
t.Errorf("expected an error but didn't get one !")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the sub functions (those will be the correct and incorrect version of this test)
|
||||
sub := func(data map[string][]byte, correct bool) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
// Declare this test to be run in parallel
|
||||
t.Parallel()
|
||||
|
||||
// If we have no data, we skip
|
||||
if len(data) == 0 {
|
||||
t.Skip("no data provided, skipping...")
|
||||
}
|
||||
|
||||
// For all files provided
|
||||
for name, datum := range data {
|
||||
// Get the run function
|
||||
rfunc := rgen(datum, correct)
|
||||
|
||||
// Run !
|
||||
t.Run(name, rfunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run !
|
||||
t.Run("correct", sub(data.correct, true))
|
||||
t.Run("incorrect", sub(data.incorrect, false))
|
||||
}
|
||||
9
types/company.go
Normal file
9
types/company.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// A Company is a provider of transport
|
||||
// Example: the RATP in Paris
|
||||
// See http://doc.navitia.io/#public-transport-objects
|
||||
type Company struct {
|
||||
ID string `json:"id"` // Identifier of the company
|
||||
Name string `json:"name"` // Name of the company
|
||||
}
|
||||
280
types/container.go
Normal file
280
types/container.go
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// these are the types that can be embedded.
|
||||
const (
|
||||
EmbeddedStopArea = "stop_area" // This is a Place & a PT Object
|
||||
EmbeddedPOI = "poi" // This is a place
|
||||
EmbeddedAddress = "address" // This is a place
|
||||
EmbeddedStopPoint = "stop_point" // This is a place
|
||||
EmbeddedAdmin = "administrative_region" // This is a place
|
||||
EmbeddedLine = "line" // This is a PT Object
|
||||
EmbeddedRoute = "route" // This is a PT Object
|
||||
EmbeddedNetwork = "network" // This is a PT Object
|
||||
EmbeddedCommercialMode = "commercial_mode" // This is a PT Object
|
||||
EmbeddedTrip = "trip" // This is a PT Object
|
||||
)
|
||||
|
||||
// EmbeddedTypes lists all the possible embedded types you can find in a Container
|
||||
var EmbeddedTypes = [...]string{
|
||||
EmbeddedStopArea,
|
||||
EmbeddedPOI,
|
||||
EmbeddedAddress,
|
||||
EmbeddedStopPoint,
|
||||
EmbeddedAdmin,
|
||||
EmbeddedLine,
|
||||
EmbeddedRoute,
|
||||
EmbeddedNetwork,
|
||||
EmbeddedCommercialMode,
|
||||
EmbeddedTrip,
|
||||
}
|
||||
|
||||
// embeddedTypesPlace stores a list of embedded types you can find in a container containing a Place.
|
||||
var embeddedTypesPlace = [...]string{
|
||||
EmbeddedStopArea,
|
||||
EmbeddedPOI,
|
||||
EmbeddedAddress,
|
||||
EmbeddedStopPoint,
|
||||
EmbeddedAdmin,
|
||||
}
|
||||
|
||||
// embeddedTypesPTObject stores a list of embedded types you can find in a container containing a PTObject.
|
||||
var embeddedTypesPTObject = [...]string{
|
||||
EmbeddedStopArea,
|
||||
EmbeddedLine,
|
||||
EmbeddedRoute,
|
||||
EmbeddedNetwork,
|
||||
EmbeddedCommercialMode,
|
||||
EmbeddedTrip,
|
||||
}
|
||||
|
||||
// An Object is what is contained by a Container
|
||||
type Object interface{}
|
||||
|
||||
// A Container holds an Object, which can be a Place or a PT Object
|
||||
type Container struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
EmbeddedType string `json:"embedded_type"`
|
||||
Quality int `json:"quality"`
|
||||
|
||||
embeddedJSON json.RawMessage
|
||||
|
||||
// embeddedObject acts as a cache, it is the only element guarded by the RWMutex
|
||||
embeddedObject Object
|
||||
|
||||
// If multiple goroutines try to access embeddedObject while one is writing it, results are undefined.
|
||||
// So we guard against it through a mutex.
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
// IsPlace returns true if the container's content is a Place
|
||||
func (c *Container) IsPlace() bool {
|
||||
t := c.EmbeddedType
|
||||
return t == EmbeddedStopArea ||
|
||||
t == EmbeddedPOI ||
|
||||
t == EmbeddedAddress ||
|
||||
t == EmbeddedStopPoint ||
|
||||
t == EmbeddedAdmin
|
||||
}
|
||||
|
||||
// IsPTObject returns true if the container's content is a PTObject
|
||||
func (c *Container) IsPTObject() bool {
|
||||
t := c.EmbeddedType
|
||||
return t == EmbeddedStopArea ||
|
||||
t == EmbeddedLine ||
|
||||
t == EmbeddedRoute ||
|
||||
t == EmbeddedNetwork ||
|
||||
t == EmbeddedCommercialMode ||
|
||||
t == EmbeddedTrip
|
||||
}
|
||||
|
||||
// ErrInvalidContainer is returned after a check on a Container
|
||||
type ErrInvalidContainer struct {
|
||||
// If the Container has a zero ID.
|
||||
NoID bool
|
||||
|
||||
// If the PlaceContainer has an EmbeddedType yet non-empty embedded content.
|
||||
NoEmbeddedType bool
|
||||
|
||||
// If the PlaceContainer has an unknown EmbeddedType
|
||||
UnknownEmbeddedType bool
|
||||
}
|
||||
|
||||
// Error satisfies the error interface
|
||||
func (err ErrInvalidContainer) Error() string {
|
||||
// Count the number of anomalies
|
||||
var anomalies uint
|
||||
|
||||
msg := "Error: Invalid non-empty PlaceContainer (%d anomalies):"
|
||||
|
||||
if err.NoID {
|
||||
msg += "\n\tNo ID specified"
|
||||
anomalies++
|
||||
}
|
||||
if err.NoEmbeddedType {
|
||||
msg += "\n\tEmpty EmbeddedType yet non-empty embedded content"
|
||||
anomalies++
|
||||
}
|
||||
if err.UnknownEmbeddedType {
|
||||
msg += "\n\tUnknown EmbeddedType"
|
||||
anomalies++
|
||||
}
|
||||
|
||||
return fmt.Sprintf(msg, anomalies)
|
||||
}
|
||||
|
||||
// Empty returns true if the container is empty (zero value)
|
||||
func (c *Container) Empty() bool {
|
||||
return c.ID == "" && c.Name == "" && c.EmbeddedType == "" && c.Quality == 0 && len(c.embeddedJSON) == 0 && c.embeddedObject == nil
|
||||
}
|
||||
|
||||
// Check checks the validity of the Container. Returns an ErrInvalidContainer.
|
||||
//
|
||||
// An empty Container is valid. But those cases aren't:
|
||||
// - If the Container has an empty ID.
|
||||
// - If the Container has an empty EmbeddedType & a non-empty embedded types inside.
|
||||
// - If the Container has an unknown EmbeddedType.
|
||||
func (c *Container) Check() error {
|
||||
// Check if the container is empty
|
||||
if c.Empty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create the error to be populated
|
||||
err := ErrInvalidContainer{}
|
||||
|
||||
// Check for zero ID
|
||||
err.NoID = c.ID == ""
|
||||
|
||||
// Check if the embedded type is empty & there is a non-empty embedded content inside, that's an error
|
||||
if c.EmbeddedType == "" && (len(c.embeddedJSON) != 0 || c.embeddedObject != nil) {
|
||||
err.NoEmbeddedType = true
|
||||
return err
|
||||
} else if c.EmbeddedType == "" { // Else, if the embedded type indicator is empty, the rest is useless
|
||||
return nil
|
||||
}
|
||||
|
||||
// Else, check if the declared EmbeddedType is known.
|
||||
var known bool
|
||||
for _, ket := range EmbeddedTypes {
|
||||
if c.EmbeddedType == ket {
|
||||
known = true
|
||||
break
|
||||
}
|
||||
}
|
||||
err.UnknownEmbeddedType = !known
|
||||
|
||||
// Check if there's any change
|
||||
emptyErr := ErrInvalidContainer{}
|
||||
if err != emptyErr {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object returns the Object contained in a Container.
|
||||
// If the Container is empty, Object returns an error.
|
||||
// Check() is run on the Container.
|
||||
func (c *Container) Object() (Object, error) {
|
||||
if c.EmbeddedType == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// If we already have an embedded object, return it
|
||||
c.mu.RLock()
|
||||
o := c.embeddedObject
|
||||
c.mu.RUnlock()
|
||||
if o != nil {
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Create the receiver
|
||||
var obj Object
|
||||
|
||||
// Switch through
|
||||
switch c.EmbeddedType {
|
||||
case EmbeddedStopArea:
|
||||
obj = &StopArea{}
|
||||
case EmbeddedPOI:
|
||||
obj = &POI{}
|
||||
case EmbeddedAddress:
|
||||
obj = &Address{}
|
||||
case EmbeddedStopPoint:
|
||||
obj = &StopPoint{}
|
||||
case EmbeddedAdmin:
|
||||
obj = &Admin{}
|
||||
case EmbeddedLine:
|
||||
obj = &Line{}
|
||||
case EmbeddedRoute:
|
||||
obj = &Route{}
|
||||
case EmbeddedNetwork:
|
||||
obj = &Network{}
|
||||
case EmbeddedCommercialMode:
|
||||
obj = &CommercialMode{}
|
||||
case EmbeddedTrip:
|
||||
obj = &Trip{}
|
||||
default:
|
||||
return nil, errors.Errorf("no known embedded type indicated (we have \"%s\"), can't return a place !", c.EmbeddedType)
|
||||
}
|
||||
|
||||
// Unmarshal into the receiver
|
||||
err := json.Unmarshal(c.embeddedJSON, obj)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "couldn't unmarshal the embedded type (%s)", c.EmbeddedType)
|
||||
}
|
||||
|
||||
// Let's add it to the container
|
||||
c.mu.Lock()
|
||||
c.embeddedObject = obj
|
||||
c.mu.Unlock()
|
||||
|
||||
// Let's return it
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Place returns the Place contained in the container if that is what's inside
|
||||
//
|
||||
// If the Object isn't a Place or the Container is empty or invalid, Place returns an error
|
||||
func (c *Container) Place() (Place, error) {
|
||||
// Check if its a Place
|
||||
if !c.IsPlace() {
|
||||
return nil, errors.Errorf("container's content isn't a Place")
|
||||
}
|
||||
|
||||
// Return it then
|
||||
obj, err := c.Object()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Type assert
|
||||
return obj.(Place), nil
|
||||
}
|
||||
|
||||
// PTObject returns the PTObject contained in the container if that is what's inside
|
||||
//
|
||||
// If the Object isn't a PTObject or the Container is empty or invalid, Place returns an error
|
||||
func (c *Container) PTObject() (PTObject, error) {
|
||||
// Check if its a Place
|
||||
if !c.IsPTObject() {
|
||||
return nil, errors.Errorf("container's content isn't a PTObject")
|
||||
}
|
||||
|
||||
// Return it then
|
||||
obj, err := c.Object()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Type assert
|
||||
return obj.(PTObject), nil
|
||||
}
|
||||
21
types/container_fuzz.go
Normal file
21
types/container_fuzz.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// +build gofuzz
|
||||
|
||||
package types
|
||||
|
||||
func FuzzContainer(data []byte) int {
|
||||
c := &Container{}
|
||||
|
||||
// Let's unmarshal, this is not our job so "bleh"
|
||||
err := c.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Let's check it !
|
||||
err = c.Check()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
57
types/container_json.go
Normal file
57
types/container_json.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// UnmarshalJSON satisfies the json.Unmarshaller interface
|
||||
func (c *Container) UnmarshalJSON(b []byte) error {
|
||||
// Set up a mutex
|
||||
c.mu = &sync.RWMutex{}
|
||||
|
||||
// Unmarshal into a map
|
||||
data := map[string]json.RawMessage{}
|
||||
err := json.Unmarshal(b, &data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Couldn't unmarshal into a map")
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"Container", b}
|
||||
|
||||
// From a map, extract the ID, Name & EmbeddedType
|
||||
if id, ok := data["id"]; ok {
|
||||
err := json.Unmarshal(id, &c.ID)
|
||||
if err != nil {
|
||||
return gen.err(err, "ID", "id", id, "error while unmarshalling")
|
||||
}
|
||||
}
|
||||
if name, ok := data["name"]; ok {
|
||||
err := json.Unmarshal(name, &c.Name)
|
||||
if err != nil {
|
||||
return gen.err(err, "Name", "name", name, "error while unmarshalling")
|
||||
}
|
||||
}
|
||||
if embeddedType, ok := data["embedded_type"]; ok {
|
||||
err := json.Unmarshal(embeddedType, &c.EmbeddedType)
|
||||
if err != nil {
|
||||
return gen.err(err, "EmbeddedType", "embedded_type", embeddedType, "error while unmarshalling")
|
||||
}
|
||||
}
|
||||
if quality, ok := data["quality"]; ok {
|
||||
err := json.Unmarshal(quality, &c.Quality)
|
||||
if err != nil {
|
||||
return gen.err(err, "Quality", "quality", quality, "error while unmarshalling")
|
||||
}
|
||||
}
|
||||
|
||||
// Now, assign the embedded content to the Container
|
||||
if embedded, ok := data[c.EmbeddedType]; ok {
|
||||
c.embeddedJSON = embedded
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
259
types/container_test.go
Normal file
259
types/container_test.go
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var containers map[string]*Container
|
||||
|
||||
// loadContainers loads the containers in their final form for testing
|
||||
func loadContainers() error {
|
||||
// Get the input
|
||||
corpus := testData["container"].correct
|
||||
if len(corpus) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cs := make(map[string]*Container, len(corpus))
|
||||
// For each of them, unmarshal and add to containers
|
||||
for name, datum := range corpus {
|
||||
c := &Container{}
|
||||
|
||||
err := c.UnmarshalJSON(datum)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling: %v", err)
|
||||
}
|
||||
|
||||
cs[name] = c
|
||||
}
|
||||
|
||||
containers = cs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestContainer_Object_NoCompare tests the Container.Object method
|
||||
func TestContainer_Object_NoCompare(t *testing.T) {
|
||||
// Get the input
|
||||
data := containers
|
||||
if len(data) == 0 {
|
||||
t.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Create the run function generator, allowing us to run it in parallel
|
||||
rgen := func(c *Container) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
// Get the object
|
||||
obj, err := c.Object()
|
||||
if err != nil {
|
||||
t.Errorf("Error while calling .Object(): %v", err)
|
||||
}
|
||||
|
||||
// Log it
|
||||
t.Logf("Object: %#v", obj)
|
||||
}
|
||||
}
|
||||
|
||||
// For each of them, let's run a subtest
|
||||
for name, datum := range data {
|
||||
// Create the run function
|
||||
rfunc := rgen(datum)
|
||||
|
||||
// Run !
|
||||
t.Run(name, rfunc)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContainer_Check_NoCompare tests the Container.Check method
|
||||
func TestContainer_Check_NoCompare(t *testing.T) {
|
||||
// Get the input
|
||||
data := containers
|
||||
if len(data) == 0 {
|
||||
t.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Create the run function generator, allowing us to run it in parallel
|
||||
rgen := func(c *Container) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
err := c.Check()
|
||||
if err != nil {
|
||||
t.Errorf("Check gave us invalid results: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each of them, let's run a subtest
|
||||
for name, datum := range data {
|
||||
// Create the run function
|
||||
rfunc := rgen(datum)
|
||||
|
||||
// Run !
|
||||
t.Run(name, rfunc)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkContainer_UnmarshalJSON benchmarks Container.UnmarshalJSON through benchmarks
|
||||
func BenchmarkContainer_UnmarshalJSON(b *testing.B) {
|
||||
// Get the bench data
|
||||
data := testData["container"].bench
|
||||
if len(data) == 0 {
|
||||
b.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Run function generator, allowing parallel run
|
||||
// Returns three functions: one without .Object() being called, one with, one one with only the call to .Object being recorded
|
||||
runGen := func(in []byte) (without func(*testing.B), with func(*testing.B), only func(*testing.B)) {
|
||||
without = func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Unmarshal a Journey
|
||||
c := &Container{}
|
||||
_ = c.UnmarshalJSON(in)
|
||||
}
|
||||
}
|
||||
|
||||
with = func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Unmarshal a Journey
|
||||
c := &Container{}
|
||||
_ = c.UnmarshalJSON(in)
|
||||
|
||||
_, _ = c.Object()
|
||||
}
|
||||
}
|
||||
|
||||
only = func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Unmarshal a Journey
|
||||
b.StopTimer()
|
||||
c := &Container{}
|
||||
_ = c.UnmarshalJSON(in)
|
||||
b.StartTimer()
|
||||
_, _ = c.Object()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Loop over all corpus
|
||||
for name, datum := range data {
|
||||
// Get run function
|
||||
without, with, only := runGen(datum)
|
||||
|
||||
// Run it !
|
||||
b.Run(name+"_without", without)
|
||||
b.Run(name+"_with", with)
|
||||
b.Run(name+"_only", only)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkContainer_Check benchmarks Container.Check through subbenchmarks
|
||||
func BenchmarkContainer_Check(b *testing.B) {
|
||||
// Get the bench data
|
||||
data := testData["container"].bench
|
||||
if len(data) == 0 {
|
||||
b.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Run function generator, allowing parallel run
|
||||
runGen := func(in Container) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Call .Check
|
||||
_ = in.Check()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all corpus
|
||||
for name, datum := range data {
|
||||
c := Container{}
|
||||
|
||||
err := json.Unmarshal(datum, &c)
|
||||
if err != nil {
|
||||
b.Errorf("Error while unmarshalling: %v", err)
|
||||
}
|
||||
|
||||
// Get run function
|
||||
runFunc := runGen(c)
|
||||
|
||||
// Run it !
|
||||
b.Run(name, runFunc)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContainer_IsXXX tests (*Container).IsPlace and (*Container).IsPTObject
|
||||
func TestContainer_IsXXX(t *testing.T) {
|
||||
t.Run("IsPlace", func(t *testing.T) {
|
||||
for _, et := range embeddedTypesPlace {
|
||||
c := &Container{EmbeddedType: et}
|
||||
if !c.IsPlace() {
|
||||
t.Errorf("IsPlace for embedded type %s: expected true got false", et)
|
||||
}
|
||||
}
|
||||
for _, et := range embeddedTypesPTObject {
|
||||
if et != EmbeddedStopArea {
|
||||
c := &Container{EmbeddedType: et}
|
||||
if c.IsPlace() {
|
||||
t.Errorf("IsPlace for embedded type %s: expected false got true", et)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IsPTObject", func(t *testing.T) {
|
||||
for _, et := range embeddedTypesPTObject {
|
||||
c := &Container{EmbeddedType: et}
|
||||
if !c.IsPTObject() {
|
||||
t.Errorf("IsPTObject for embedded type %s: expected true got false", et)
|
||||
}
|
||||
}
|
||||
for _, et := range embeddedTypesPlace {
|
||||
if et != EmbeddedStopArea {
|
||||
c := &Container{EmbeddedType: et}
|
||||
if c.IsPTObject() {
|
||||
t.Errorf("IsPTObject for embedded type %s: expected false got true", et)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestContainer_Empty tests that (*Container).Empty reports correctly whether or not the container is truly empty
|
||||
func TestContainer_Empty(t *testing.T) {
|
||||
emptyContainer := Container{}
|
||||
nonEmptyContainers := []Container{
|
||||
{
|
||||
ID: "test",
|
||||
},
|
||||
{
|
||||
Name: "test",
|
||||
},
|
||||
{
|
||||
EmbeddedType: "test",
|
||||
},
|
||||
{
|
||||
Quality: 10,
|
||||
},
|
||||
{
|
||||
embeddedObject: new(Object),
|
||||
},
|
||||
{
|
||||
embeddedJSON: json.RawMessage("that's not very raw"),
|
||||
},
|
||||
}
|
||||
|
||||
// If the empty container isn't reported as such, error
|
||||
if !emptyContainer.Empty() {
|
||||
t.Errorf("Calling (*Container).Empty on an empty container returned with false when we expected true")
|
||||
}
|
||||
|
||||
// Iterate through the test non-empty containers
|
||||
for _, c := range nonEmptyContainers {
|
||||
if c.Empty() {
|
||||
t.Errorf("Calling (*Container).Empty on a non-empty container returned true when we expected false. Container: %#v", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/coord.go
Normal file
51
types/coord.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Coordinates code for coordinates used throughout the API.
|
||||
// This is the Go representation of "Coordinates". It implements Place.
|
||||
// See http://doc.navitia.io/#standard-objects.
|
||||
type Coordinates struct {
|
||||
Longitude float64 `json:"lon"`
|
||||
Latitude float64 `json:"lat"`
|
||||
}
|
||||
|
||||
// jsonCoordinates define the JSON implementation of Coordinates types
|
||||
type jsonCoordinates struct {
|
||||
Latitude string `json:"lat"`
|
||||
Longitude string `json:"lon"`
|
||||
}
|
||||
|
||||
// ID formats coordinates for use in queries as an ID.
|
||||
func (c Coordinates) ID() ID {
|
||||
return ID(fmt.Sprintf("%3.3f;%3.3f", c.Longitude, c.Latitude))
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Coordinates
|
||||
func (c *Coordinates) UnmarshalJSON(b []byte) error {
|
||||
var data jsonCoordinates
|
||||
|
||||
err := json.Unmarshal(b, &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Coordinates types : %w", err)
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"Coordinates", b}
|
||||
|
||||
// Now parse the values
|
||||
c.Longitude, err = strconv.ParseFloat(data.Longitude, 64)
|
||||
if err != nil {
|
||||
return gen.err(err, "Longitude", "lon", data.Longitude, "error in strconv.ParseFloat")
|
||||
}
|
||||
c.Latitude, err = strconv.ParseFloat(data.Latitude, 64)
|
||||
if err != nil {
|
||||
return gen.err(err, "Latitude", "lat", data.Latitude, "error in strconv.ParseFloat")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
18
types/departure.go
Normal file
18
types/departure.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package types
|
||||
|
||||
type Departure struct {
|
||||
DisplayInformations Display `json:"display_informations"`
|
||||
StopPoint StopPoint `json:"stop_point"`
|
||||
Route Route `json:"route"`
|
||||
Links []Link `json:"links"`
|
||||
StopDateTime
|
||||
}
|
||||
|
||||
type StopDateTime struct {
|
||||
Links []Link `json:"links"`
|
||||
ArrivalDateTime string `json:"arrival_date_time"`
|
||||
DepartureDateTime string `json:"departure_date_time"`
|
||||
BaseArrivalDateTime string `json:"base_arrival_date_time"`
|
||||
BaseDepartureDateTime string `json:"base_departure_date_time"`
|
||||
DataFreshness string `json:"data_freshness"`
|
||||
}
|
||||
89
types/display.go
Normal file
89
types/display.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// A Display holds informations useful to display.
|
||||
type Display struct {
|
||||
Headsign string `json:"headsign"` // The headsign associated with the object
|
||||
Network string `json:"network"` // The name of the belonging network
|
||||
Direction string `json:"direction"` // A direction to take
|
||||
CommercialMode ID `json:"commercial_mode"` // The commercial mode in ID Form
|
||||
PhysicalMode ID `json:"physical_mode"` // The physical mode in ID Form
|
||||
Label string `json:"label"` // The label of the object
|
||||
Color color.Color `json:"color"` // Hexadecimal color of the line
|
||||
TextColor color.Color `json:"text_color"` // The text color for this section
|
||||
Code string `json:"code"` // The code of the line
|
||||
Description string `json:"description"` // Description
|
||||
Equipments []Equipment `json:"equipments"` // Equipments on this object
|
||||
Name string `json:"name"` // Name of object
|
||||
TripShortName string `json:"trip_short_name"` // TripShoerName short name of the current trip
|
||||
}
|
||||
|
||||
// jsonDisplay define the JSON implementation of Display types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonDisplay struct {
|
||||
// Pointers to the corresponding real values
|
||||
Headsign *string `json:"headsign"`
|
||||
Network *string `json:"network"`
|
||||
Direction *string `json:"direction"`
|
||||
CommercialMode *ID `json:"commercial_mode"`
|
||||
PhysicalMode *ID `json:"physical_mode"`
|
||||
Label *string `json:"label"`
|
||||
Code *string `json:"code"`
|
||||
Description *string `json:"description"`
|
||||
Equipments *[]Equipment `json:"equipments"`
|
||||
|
||||
// Values to process
|
||||
Color string `json:"color"`
|
||||
TextColor string `json:"text_color"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Display
|
||||
func (d *Display) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
// We define some of the value as pointers to the real values, allowing us to bypass copying in cases where we don't need to process the data
|
||||
data := &jsonDisplay{
|
||||
Headsign: &d.Headsign,
|
||||
Network: &d.Network,
|
||||
Direction: &d.Direction,
|
||||
CommercialMode: &d.CommercialMode,
|
||||
PhysicalMode: &d.PhysicalMode,
|
||||
Label: &d.Label,
|
||||
Code: &d.Code,
|
||||
Description: &d.Description,
|
||||
Equipments: &d.Equipments,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Display: %w", err)
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"Display", b}
|
||||
|
||||
// Now process the values
|
||||
// We expect a color string length of 6 because it should be coded in hexadecimal
|
||||
if str := data.Color; len(str) == 6 {
|
||||
clr, err := parseColor(str)
|
||||
if err != nil {
|
||||
return gen.err(err, "Color", "color", str, "error in parseColor")
|
||||
}
|
||||
d.Color = clr
|
||||
}
|
||||
if str := data.TextColor; len(str) == 6 {
|
||||
clr, err := parseColor(str)
|
||||
if err != nil {
|
||||
return gen.err(err, "TextColor", "text_color", str, "error in parseColor")
|
||||
}
|
||||
d.TextColor = clr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
137
types/disruption.go
Normal file
137
types/disruption.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Effect codes for known journey status information
|
||||
// For example, reduced service, detours or moved stops.
|
||||
//
|
||||
// See https://developers.google.com/transit/gtfs-realtime/reference/Effect for more information
|
||||
type Effect string
|
||||
|
||||
// JourneyStatusXXX are known JourneyStatuse
|
||||
const (
|
||||
// Service suspended.
|
||||
EffectNoService Effect = "NO_SERVICE"
|
||||
|
||||
// Service running at lowered capacity.
|
||||
JourneyStatusReducedService = "REDUCED_SERVICE"
|
||||
|
||||
// Service running but with substantial delays expected.
|
||||
JourneyStatusSignificantDelay = "SIGNIFICANT_DELAY"
|
||||
|
||||
// Service running on alternative routes to avoid problem.
|
||||
JourneyStatusDetour = "DETOUR"
|
||||
|
||||
// Service above normal capacity.
|
||||
JourneyStatusAdditionalService = "ADDITIONAL_SERVICE"
|
||||
|
||||
// Service different from normal capacity.
|
||||
JourneyStatusModifiedService = "MODIFIED_SERVICE"
|
||||
|
||||
// Miscellaneous, undefined Effect.
|
||||
JourneyStatusOtherEffect = "OTHER_EFFECT"
|
||||
|
||||
// Default setting: Undetermined or Effect not known.
|
||||
JourneyStatusUnknownEffect = "UNKNOWN_EFFECT"
|
||||
|
||||
// Stop not at previous location or stop no longer on route.
|
||||
JourneyStatusStopMoved = "STOP_MOVED"
|
||||
)
|
||||
|
||||
type DisruptionStatus string
|
||||
|
||||
const (
|
||||
StatusPast DisruptionStatus = "past"
|
||||
StatusActive = "active"
|
||||
StatusFuture = "future"
|
||||
)
|
||||
|
||||
// A Disruption reports the specifics of a Disruption
|
||||
type Disruption struct {
|
||||
ID ID `json:"id"` // ID of the Disruption
|
||||
|
||||
// State of the disruption.
|
||||
// The state is computed using the application_periods of the disruption and the current time of the query.
|
||||
// It can be either "Past", "Active" or "Future"
|
||||
Status DisruptionStatus `json:"status"`
|
||||
Tags []string `json:"tags"`
|
||||
|
||||
InputDisruptionID ID // For traceability, ID of original input disruption
|
||||
InputImpactID ID // For traceability: Id of original input impact
|
||||
Severity Severity `json:"severity"` // Severity gives some categorization element
|
||||
Periods []Period // Dates where the current disruption is active
|
||||
Messages []Message // Text to provide to the traveller
|
||||
LastUpdated time.Time // Last Update of that disruption
|
||||
Impacted []ImpactedObject `json:"impacted_stops"` // Objects impacted
|
||||
Cause string // The cause of that disruption
|
||||
Category string // The category of the disruption, optional.
|
||||
DisruptionID string `json:"disruption_id"`
|
||||
}
|
||||
|
||||
// jsonDisruption define the JSON implementation of Disruption types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonDisruption struct {
|
||||
// The references
|
||||
ID *ID `json:"id"`
|
||||
Status *DisruptionStatus `json:"status"`
|
||||
Tags *[]string `json:"tags"`
|
||||
InputDisruptionID *ID `json:"disruption_id"`
|
||||
InputImpactID *ID `json:"impact_id"`
|
||||
Severity *Severity `json:"severity"`
|
||||
Periods *[]Period `json:"application_periods"`
|
||||
Messages *[]Message `json:"messages"`
|
||||
Impacted *[]ImpactedObject `json:"impacted_objects"`
|
||||
Cause *string `json:"cause"`
|
||||
Category *string `json:"category"`
|
||||
|
||||
// Those we will process
|
||||
LastUpdated string `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PTObjectDisruptions struct {
|
||||
EmbeddedType string `json:"embedded_type"`
|
||||
ID string `json:"id"`
|
||||
Quality int `json:"quality"`
|
||||
Name string `json:"name"`
|
||||
Trip Trip `json:"trip"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Disruption
|
||||
func (d *Disruption) UnmarshalJSON(b []byte) error {
|
||||
data := &jsonDisruption{
|
||||
ID: &d.ID,
|
||||
Status: &d.Status,
|
||||
Tags: &d.Tags,
|
||||
InputDisruptionID: &d.InputDisruptionID,
|
||||
InputImpactID: &d.InputImpactID,
|
||||
Severity: &d.Severity,
|
||||
Periods: &d.Periods,
|
||||
Messages: &d.Messages,
|
||||
Impacted: &d.Impacted,
|
||||
Cause: &d.Cause,
|
||||
Category: &d.Category,
|
||||
}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"Disruption", b}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Disruption: %w", err)
|
||||
}
|
||||
|
||||
// Now we process the Update time
|
||||
d.LastUpdated, err = parseDateTime(data.LastUpdated)
|
||||
if err != nil {
|
||||
return gen.err(err, "LastUpdated", "updated_at", data.LastUpdated, "parseDateTime failed")
|
||||
}
|
||||
|
||||
// Finished !
|
||||
return nil
|
||||
}
|
||||
16
types/disruption_test.go
Normal file
16
types/disruption_test.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Disruption_Unmarshal tests unmarshalling for Disruption.
|
||||
// As the unmarshalling is done in-house, this allows us to check that the custom UnmarshalJSON function correctly
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func Test_Disruption_Unmarshal(t *testing.T) {
|
||||
testUnmarshal(t, testData["disruption"], reflect.TypeOf(Disruption{}))
|
||||
}
|
||||
48
types/equipment.go
Normal file
48
types/equipment.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package types
|
||||
|
||||
// An Equipment codes for specific equipment the public transport object has
|
||||
type Equipment string
|
||||
|
||||
// EquipmentWheelchairAccessibility are known equipments
|
||||
const (
|
||||
EquipmentWheelchairAccessibility Equipment = "has_wheelchair_accessibility"
|
||||
EquipmentBikeAccepted Equipment = "has_bike_accepted"
|
||||
EquipmentAirConditioned Equipment = "has_air_conditioned"
|
||||
EquipmentVisualAnnouncement Equipment = "has_visual_announcement"
|
||||
EquipmentAudibleAnnouncement Equipment = "has_audible_announcement"
|
||||
EquipmentAppropriateEscort Equipment = "has_appropriate_escort"
|
||||
EquipmentAppropriateSignage Equipment = "has_appropriate_signage"
|
||||
EquipmentSchoolVehicle Equipment = "has_school_vehicle"
|
||||
EquipmentWheelchairBoarding Equipment = "has_wheelchair_boarding"
|
||||
EquipmentSheltered Equipment = "has_sheltered"
|
||||
EquipmentElevator Equipment = "has_elevator"
|
||||
EquipmentEscalator Equipment = "has_escalator"
|
||||
EquipmentBikeDepot Equipment = "has_bike_depot"
|
||||
)
|
||||
|
||||
// knownEquipments lists all the known equipments
|
||||
var knownEquipments = [...]Equipment{
|
||||
EquipmentWheelchairAccessibility,
|
||||
EquipmentBikeAccepted,
|
||||
EquipmentAirConditioned,
|
||||
EquipmentVisualAnnouncement,
|
||||
EquipmentAudibleAnnouncement,
|
||||
EquipmentAppropriateEscort,
|
||||
EquipmentAppropriateSignage,
|
||||
EquipmentSchoolVehicle,
|
||||
EquipmentWheelchairBoarding,
|
||||
EquipmentSheltered,
|
||||
EquipmentElevator,
|
||||
EquipmentEscalator,
|
||||
EquipmentBikeDepot,
|
||||
}
|
||||
|
||||
// Known reports whether an equipment is known
|
||||
func (eq Equipment) Known() bool {
|
||||
for _, k := range knownEquipments {
|
||||
if eq == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
6
types/exception.go
Normal file
6
types/exception.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package types
|
||||
|
||||
type Exception struct {
|
||||
Type string `json:"type"`
|
||||
Datetime string `json:"datetime"`
|
||||
}
|
||||
54
types/fare.go
Normal file
54
types/fare.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/text/currency"
|
||||
)
|
||||
|
||||
// Fare is the fare of some thing
|
||||
type Fare struct {
|
||||
Total currency.Amount
|
||||
Found bool
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Fare
|
||||
func (f *Fare) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
// We define some of the value as pointers to the real values, allowing us to bypass copying in cases where we don't need to process the data
|
||||
data := &struct {
|
||||
Found *bool `json:"found"`
|
||||
Cost struct {
|
||||
Value string `json:"value"`
|
||||
Currency string `json:"currency"`
|
||||
} `json:"cost"`
|
||||
}{
|
||||
Found: &f.Found,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
if err := json.Unmarshal(b, data); err != nil {
|
||||
return errors.Wrap(err, "Error while unmarshalling journey")
|
||||
}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"Fare", b}
|
||||
|
||||
// Let's convert the cost now
|
||||
// If we have no defined fare, let's skip that part
|
||||
if data.Cost.Value == "" || data.Cost.Currency == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// First get the currency unit
|
||||
unit, err := currency.ParseISO(data.Cost.Currency)
|
||||
if err != nil {
|
||||
return gen.err(err, "Total", "cost.currency", data.Cost.Currency, "error while retrieving currency unit via currency.ParseISO")
|
||||
}
|
||||
|
||||
// Now let's create the correct amount
|
||||
f.Total = unit.Amount(data.Cost.Value)
|
||||
|
||||
return nil
|
||||
}
|
||||
5
types/fareZone.go
Normal file
5
types/fareZone.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package types
|
||||
|
||||
type FareZone struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
43
types/fuzz.sh
Executable file
43
types/fuzz.sh
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash
|
||||
shopt -s extglob
|
||||
declare -A functionNames
|
||||
functionNames=(["FuzzJourney"]="journey" ["FuzzPlaceCountainer"]="place")
|
||||
|
||||
echo "Functions that will be built:"
|
||||
printf "\t- %s\n" "${!functionNames[@]}"
|
||||
|
||||
workdirPath="/tmp/fuzz"
|
||||
echo "Creating global workdir: $workdirPath"
|
||||
mkdir $workdirPath
|
||||
|
||||
for i in "${!functionNames[@]}"
|
||||
do
|
||||
path="$workdirPath/$i"
|
||||
mkdir $path
|
||||
mkdir "$path/corpus"
|
||||
binpath="$path/$i.zip"
|
||||
|
||||
corpuspath="./testdata/${functionNames[$i]}"
|
||||
echo "Copying corpus for $i from $corpuspath"
|
||||
cp $corpuspath/known/* "$path/corpus/"
|
||||
cp $corpuspath/corpus/* "$path/corpus/"
|
||||
|
||||
echo "Building $i"
|
||||
go-fuzz-build -func $i -o $binpath github.com/aabizri/gonavitia/types
|
||||
|
||||
echo "Running $i ($binpath)"
|
||||
go-fuzz -bin $binpath -workdir=$path
|
||||
|
||||
echo "Copying back corpus from $corpuspath"
|
||||
destination="./testdata/${functionNames[$i]}/corpus"
|
||||
rsync --exclude="*.json" $path/corpus/* $destination/
|
||||
|
||||
echo "Copying crashers"
|
||||
destination="./testdata/${functionNames[$i]}/crasher"
|
||||
commit=`git rev-parse HEAD`
|
||||
for j in $path/crashers/*
|
||||
do
|
||||
filename=$commit-${j##*/}
|
||||
cp $j $destination/$filename
|
||||
done
|
||||
done
|
||||
50
types/id.go
Normal file
50
types/id.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// An ID is used throughout the library, it is something used by the navitia API and useful to communicate with it.
|
||||
type ID string
|
||||
|
||||
// Check for ID validity
|
||||
func (id ID) Check() error {
|
||||
if len(id) == 0 {
|
||||
return errors.Errorf("ID invalid: an empty string \"\" is not a valid ID")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeNames stores navitia-side name of types that may appear in IDs
|
||||
var typeNames = map[string]bool{
|
||||
"network": true,
|
||||
"line": true,
|
||||
"route": true,
|
||||
"stop_area": true,
|
||||
"commercial_mode": true,
|
||||
"physical_mode": true,
|
||||
"company": true,
|
||||
"admin": true,
|
||||
"stop_point": true,
|
||||
}
|
||||
|
||||
// Type gets the type of object this ID refers to.
|
||||
//
|
||||
// Possible types: network, line, route, stop_area, commercial_mode, physical_mode, company, admin, stop_point.
|
||||
//
|
||||
// This is just guessing, if no type is found, type returns an empty string.
|
||||
func (id ID) Type() string {
|
||||
splitted := strings.Split(string(id), ":")
|
||||
if len(splitted) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
possible := splitted[0]
|
||||
if typeNames[possible] {
|
||||
return possible
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
11
types/id_test.go
Normal file
11
types/id_test.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestIDCheck checks if ID.Check returns an error when given an empty ID
|
||||
func TestIDCheck(t *testing.T) {
|
||||
id := ID("")
|
||||
if id.Check() == nil {
|
||||
t.Errorf("Received no error even though we expect one")
|
||||
}
|
||||
}
|
||||
13
types/impactedObject.go
Normal file
13
types/impactedObject.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package types
|
||||
|
||||
// An ImpactedObject describes a PTObject impacted by a Disruption with some additional info.
|
||||
type ImpactedObject struct {
|
||||
// The impacted public transport object
|
||||
Object Container `json:"pt_object"`
|
||||
|
||||
// Only for line section impact, the impacted section
|
||||
ImpactedSection ImpactedSection `json:"impacted_section"`
|
||||
|
||||
// Only for Trip delay, the list of delays, stop by stop
|
||||
ImpactedStops []ImpactedStop `json:"impacted_stops"`
|
||||
}
|
||||
9
types/impactedSection.go
Normal file
9
types/impactedSection.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// An ImpactedSection records the impact to a section
|
||||
type ImpactedSection struct {
|
||||
// The start of the disruption, spatially
|
||||
From Container `json:"from"`
|
||||
// Until this point
|
||||
To Container `json:"to"`
|
||||
}
|
||||
42
types/impactedStop.go
Normal file
42
types/impactedStop.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package types
|
||||
|
||||
// An ImpactedStop records the impact to a stop
|
||||
type ImpactedStop struct {
|
||||
// The impacted stop point of the trip
|
||||
Point StopPoint `json:"stop_point"`
|
||||
|
||||
// New departure hour (format HHMMSS) of the trip on this stop point
|
||||
NewDeparture string
|
||||
|
||||
// New arrival hour (format HHMMSS) of the trip on this stop point
|
||||
NewArrival string
|
||||
|
||||
// Base departure hour (format HHMMSS) of the trip on this stop point
|
||||
BaseDeparture string
|
||||
|
||||
// Base arrival hour (format HHMMSS) of the trip on this stop point
|
||||
BaseArrival string
|
||||
|
||||
// Cause of the modification
|
||||
Cause string `json:"cause"`
|
||||
|
||||
// Effect on that StopPoint
|
||||
// Can be "added", "deleted", "delayed"
|
||||
Effect string
|
||||
|
||||
AmendedArrivalTime string `json:"amended_arrival_time"`
|
||||
|
||||
StopTimeEffect string `json:"stop_time_effect"`
|
||||
|
||||
DepartureStatus string `json:"departure_status"`
|
||||
|
||||
IsDetour bool `json:"is_detour"`
|
||||
|
||||
AmendedDepartureTime string `json:"amended_departure_time"`
|
||||
|
||||
BaseArrivalTime string `json:"base_arrival_time"`
|
||||
|
||||
BaseDepartureTime string `json:"base_departure_time"`
|
||||
|
||||
ArrivalStatus string `json:"arrival_status"`
|
||||
}
|
||||
10
types/isochrone.go
Normal file
10
types/isochrone.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package types
|
||||
|
||||
import "github.com/paulmach/go.geojson"
|
||||
|
||||
// An Isochrone is sent back by the /isochrones service, it gives you a multi-polygon geojson response which represent a same time travel zone.
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Isochrone_map for what is an isochrone.
|
||||
//
|
||||
// See http://doc.navitia.io/#isochrones-currently-in-beta
|
||||
type Isochrone geojson.Geometry
|
||||
9
types/journeyPattern.go
Normal file
9
types/journeyPattern.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// JourneyPattern A journey pattern is an ordered list of stop points.
|
||||
// Two vehicles that serve exactly the same stop points in
|
||||
// exactly the same order belong to to the same journey pattern.
|
||||
type JourneyPattern struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
195
types/journeys.go
Normal file
195
types/journeys.go
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A JourneyQualification qualifies a Journey, see const declaration.
|
||||
type JourneyQualification string
|
||||
|
||||
// JourneyXXX qualify journeys
|
||||
const (
|
||||
JourneyBest JourneyQualification = "best"
|
||||
JourneyRapid JourneyQualification = "rapid"
|
||||
JourneyComfort JourneyQualification = "comfort"
|
||||
JourneyCar JourneyQualification = "car"
|
||||
JourneyLessWalk JourneyQualification = "less_fallback_walk"
|
||||
JourneyLessBike JourneyQualification = "less_fallback_bike"
|
||||
JourneyLessBikeShare JourneyQualification = "less_fallback_bss"
|
||||
JourneyFastest JourneyQualification = "fastest"
|
||||
JourneyNoPTWalk JourneyQualification = "non_pt_walk"
|
||||
JourneyNoPTBike JourneyQualification = "non_pt_bike"
|
||||
JourneyNoPTBikeShare JourneyQualification = "non_pt_bss"
|
||||
)
|
||||
|
||||
// JourneyQualifications is a user-friendly slice of all journey qualification
|
||||
// As it might be used in requests, this is exported
|
||||
var JourneyQualifications = []JourneyQualification{
|
||||
JourneyBest,
|
||||
JourneyRapid,
|
||||
JourneyComfort,
|
||||
JourneyCar,
|
||||
JourneyLessWalk,
|
||||
JourneyLessBike,
|
||||
JourneyLessBikeShare,
|
||||
JourneyFastest,
|
||||
JourneyNoPTWalk,
|
||||
JourneyNoPTBike,
|
||||
JourneyNoPTBikeShare,
|
||||
}
|
||||
|
||||
// A Journey holds information about a possible journey
|
||||
type Journey struct {
|
||||
Duration time.Duration
|
||||
Transfers uint
|
||||
|
||||
Departure time.Time
|
||||
Requested time.Time
|
||||
Arrival time.Time
|
||||
|
||||
CO2Emissions CO2Emissions
|
||||
|
||||
Sections []Section
|
||||
|
||||
From Container
|
||||
To Container
|
||||
|
||||
Type JourneyQualification
|
||||
|
||||
Fare Fare
|
||||
|
||||
// Status from the whole journey taking into acount the most disturbing information retrieved on every object used
|
||||
Status Effect
|
||||
}
|
||||
|
||||
// jsonJourney define the JSON implementation of Journey types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonJourney struct {
|
||||
Duration int64 `json:"duration"`
|
||||
Transfers *uint `json:"nb_transfers"`
|
||||
|
||||
Departure string `json:"departure_date_time"`
|
||||
Requested string `json:"requested_date_time"`
|
||||
Arrival string `json:"arrival_date_time"`
|
||||
|
||||
Sections *[]Section `json:"sections"`
|
||||
|
||||
From *Container `json:"from"`
|
||||
To *Container `json:"to"`
|
||||
|
||||
Type *JourneyQualification `json:"type"`
|
||||
|
||||
Fare *Fare `json:"fare"`
|
||||
|
||||
Status *Effect `json:"status"`
|
||||
}
|
||||
|
||||
// CO2Emissions holds how much CO2 is emitted.
|
||||
type CO2Emissions struct {
|
||||
Unit string
|
||||
Value float64
|
||||
}
|
||||
|
||||
// jsonCO2Emissions define the JSON implementation of CO2Emissions types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonCO2Emissions struct {
|
||||
Unit *string `json:"unit"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// TravelerType is a Traveler's type
|
||||
// Defines speeds & accessibility values for different types of people
|
||||
type TravelerType string
|
||||
|
||||
// The defined types of the api
|
||||
const (
|
||||
// A standard Traveler
|
||||
TravelerStandard TravelerType = "standard"
|
||||
|
||||
// A slow walker
|
||||
TravelerSlowWalker TravelerType = "slow_walker"
|
||||
|
||||
// A fast walker
|
||||
TravelerFastWalker TravelerType = "fast_walker"
|
||||
|
||||
// A Traveler with luggage
|
||||
TravelerWithLuggage TravelerType = "luggage"
|
||||
|
||||
// A Traveler in a wheelchair
|
||||
TravelerInWheelchair TravelerType = "wheelchair"
|
||||
)
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Journey.
|
||||
// Behaviour:
|
||||
// - If "from" is empty, then don't populate the From field.
|
||||
// - Same for "to"
|
||||
func (j *Journey) UnmarshalJSON(b []byte) error {
|
||||
data := &jsonJourney{
|
||||
Transfers: &j.Transfers,
|
||||
Sections: &j.Sections,
|
||||
From: &j.From,
|
||||
To: &j.To,
|
||||
Type: &j.Type,
|
||||
Fare: &j.Fare,
|
||||
Status: &j.Status,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
if err := json.Unmarshal(b, data); err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Journey: %w", err)
|
||||
}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"Journey", b}
|
||||
|
||||
// As the given duration is in second, let's multiply it by one second to have the correct value
|
||||
j.Duration = time.Duration(data.Duration) * time.Second
|
||||
|
||||
var err error
|
||||
// For departure, requested and arrival, we use parseDateTime
|
||||
j.Departure, err = parseDateTime(data.Departure)
|
||||
if err != nil {
|
||||
return gen.err(err, "Departure", "departure_date_time", data.Departure, "parseDateTime failed")
|
||||
}
|
||||
j.Requested, err = parseDateTime(data.Requested)
|
||||
if err != nil {
|
||||
return gen.err(err, "Requested", "requested_date_time", data.Requested, "parseDateTime failed")
|
||||
}
|
||||
j.Arrival, err = parseDateTime(data.Arrival)
|
||||
if err != nil {
|
||||
return gen.err(err, "Arrival", "arrival_date_time", data.Arrival, "parseDateTime failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for CO2Emissions
|
||||
func (c *CO2Emissions) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
// We define some of the value as pointers to the real values, allowing us to bypass copying in cases where we don't need to process the data
|
||||
data := &jsonCO2Emissions{
|
||||
Unit: &c.Unit,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
if err := json.Unmarshal(b, data); err != nil {
|
||||
return fmt.Errorf("error while unmarshalling CO2Emissions: %w", err)
|
||||
}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"CO2Emissions", b}
|
||||
|
||||
// Now parse the value
|
||||
f, err := strconv.ParseFloat(data.Value, 64)
|
||||
if err != nil {
|
||||
return gen.err(err, "Value", "value", data.Value, "error in strconv.ParseFloat")
|
||||
}
|
||||
c.Value = f
|
||||
|
||||
return nil
|
||||
}
|
||||
15
types/journeys_fuzz.go
Normal file
15
types/journeys_fuzz.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// +build gofuzz
|
||||
|
||||
package types
|
||||
|
||||
func FuzzJourney(data []byte) int {
|
||||
j := &Journey{}
|
||||
|
||||
// Let's unmarshal
|
||||
err := j.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
45
types/journeys_test.go
Normal file
45
types/journeys_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Journey_Unmarshal tests unmarshalling for Journey.
|
||||
// As the unmarshalling is done in-house, this allows us to check that the custom UnmarshalJSON function correctly
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func Test_Journey_Unmarshal(t *testing.T) {
|
||||
testUnmarshal(t, testData["journey"], reflect.TypeOf(Journey{}))
|
||||
}
|
||||
|
||||
// BenchmarkJourney_UnmarshalJSON benchmarks Journey unmarshalling via subbenchmarks
|
||||
func BenchmarkJourney_UnmarshalJSON(b *testing.B) {
|
||||
// Get the bench data
|
||||
data := testData["journey"].bench
|
||||
if len(data) == 0 {
|
||||
b.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Run function generator, allowing parallel run
|
||||
runGen := func(in []byte) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Unmarshal a Journey
|
||||
j := &Journey{}
|
||||
_ = j.UnmarshalJSON(in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all corpus
|
||||
for name, datum := range data {
|
||||
// Get run function
|
||||
runFunc := runGen(datum)
|
||||
|
||||
// Run it !
|
||||
b.Run(name, runFunc)
|
||||
}
|
||||
}
|
||||
110
types/json.go
Normal file
110
types/json.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// json.go provides types & functions for json unmarshalling
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// DateTimeFormat is the format used by the Navitia Api for use with time pkg.
|
||||
DateTimeFormat string = "20060102T150405" // YYYYMMDDThhmmss
|
||||
// DateFormat is when there is no time info
|
||||
DateFormat string = "20060102"
|
||||
)
|
||||
|
||||
// parseDateTime parses a time formatted under iso-date-time as indicated in the Navitia api.
|
||||
// This is simply parsing a date formatted under the standard ISO 8601.
|
||||
// If the given string is empty (i.e ""), then the zero value of time.Time will be returned
|
||||
func parseDateTime(datetime string) (time.Time, error) {
|
||||
// If there's no datetime given, just return the zero value
|
||||
if datetime == "" || datetime == "not-a-date-time" {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
// If the datetime doesn't countain a "T", then it does not have time info
|
||||
var format string
|
||||
if strings.Contains(datetime, "T") {
|
||||
format = DateTimeFormat
|
||||
} else {
|
||||
format = DateFormat
|
||||
}
|
||||
|
||||
// Parse it
|
||||
res, err := time.Parse(format, datetime)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "parseDateTime: error while parsing datetime")
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// UnmarshalError is returned when unmarshalling fails
|
||||
// It implements both error and github.com/pkg/errors's causer
|
||||
type UnmarshalError struct {
|
||||
// Type on which the unmarshaller where the error occurred works
|
||||
Type string
|
||||
|
||||
// JSON Key where failure occurred
|
||||
Key string
|
||||
|
||||
// Name of the key in package
|
||||
Name string
|
||||
|
||||
// Value associated with the key
|
||||
Value interface{}
|
||||
|
||||
// Message of the error
|
||||
Message string
|
||||
|
||||
// Underlying error
|
||||
Underlying error
|
||||
|
||||
// Full JSON data
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
// Cause implements github.com/pkg/error's causer
|
||||
func (err UnmarshalError) Cause() error {
|
||||
return err.Underlying
|
||||
}
|
||||
|
||||
// Error implements error
|
||||
func (err UnmarshalError) Error() string {
|
||||
msg := fmt.Sprintf(
|
||||
"(*%s).UnmarshalJSON: Unmarshalling %s (json: \"%s\") with value \"%v\" failed",
|
||||
err.Type,
|
||||
err.Name,
|
||||
err.Key,
|
||||
err.Value)
|
||||
|
||||
if err.Message != "" {
|
||||
msg += ": " + err.Message
|
||||
}
|
||||
if err.Underlying != nil {
|
||||
msg += " [" + err.Cause().Error() + "]"
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// unmarshalErrorer allows us to make better error messages
|
||||
type unmarshalErrorMaker struct {
|
||||
Type string
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
// err creates a new UnmarshalError
|
||||
func (gen unmarshalErrorMaker) err(underlyingErr error, name string, key string, value interface{}, message string) error {
|
||||
return UnmarshalError{
|
||||
Type: gen.Type,
|
||||
Key: key,
|
||||
Name: name,
|
||||
Value: value,
|
||||
Message: message,
|
||||
Underlying: underlyingErr,
|
||||
JSON: gen.JSON,
|
||||
}
|
||||
}
|
||||
128
types/line.go
Normal file
128
types/line.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// A Line codes for a public transit line.
|
||||
// Warning: a Line isn't a route, it has no direction information, and can have several embranchments.
|
||||
// See http://doc.navitia.io/#public-transport-objects.
|
||||
type Line struct {
|
||||
ID ID `json:"id"` // ID is the navitia identifier of the line, eg: "line:RAT:M6"
|
||||
Name string `json:"name"` // Name of the line eg: "Nation - Charles de Gaule Etoile"
|
||||
Code string `json:"code"` // Code is the codename of the line
|
||||
Color color.Color `json:"color"` // Color of the Line, eg "FFFFFF"
|
||||
|
||||
// OpeningTime is the opening time of the line
|
||||
OpeningTime struct {
|
||||
Hours uint8 `json:"hours"`
|
||||
Minutes uint8 `json:"minutes"`
|
||||
Seconds uint8 `json:"seconds"`
|
||||
} `json:"opening_time"`
|
||||
|
||||
// ClosingTime is the closing time of the line
|
||||
ClosingTime struct {
|
||||
Hours uint8 `json:"hours"`
|
||||
Minutes uint8 `json:"minutes"`
|
||||
Seconds uint8 `json:"seconds"`
|
||||
} `json:"closing_time"`
|
||||
|
||||
Routes []Route `json:"routes"` // Routes contains the routes of the line
|
||||
CommercialMode CommercialMode `json:"commercial_mode"` // CommercialMode of the line
|
||||
PhysicalModes []PhysicalMode `json:"physical_modes"` // PhysicalModes of the line
|
||||
}
|
||||
|
||||
// jsonLine define the JSON implementation of Line types.
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonLine struct {
|
||||
ID *ID `json:"id"` // ID is the navitia identifier of the line, eg: "line:RAT:M6"
|
||||
Name *string `json:"name"` // Name of the line eg: "Nation - Charles de Gaule Etoile"
|
||||
Code *string `json:"code"` // Code is the codename of the line
|
||||
Routes *[]Route `json:"routes"` // Routes contains the routes of the line
|
||||
CommercialMode *CommercialMode `json:"commercial_mode"` // CommercialMode of the line
|
||||
PhysicalModes *[]PhysicalMode `json:"physical_modes"` // PhysicalModes of the line
|
||||
|
||||
// Value to process
|
||||
Color string `json:"color"` // Color of the Line, eg "FFFFFF"
|
||||
OpeningTime string `json:"opening_time"` // OpeningTime is the opening time of the line
|
||||
ClosingTime string `json:"closing_time"` // ClosingTime is the closing time of the line
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Line
|
||||
func (l *Line) UnmarshalJSON(b []byte) error {
|
||||
data := jsonLine{
|
||||
ID: &l.ID,
|
||||
Name: &l.Name,
|
||||
Code: &l.Code,
|
||||
Routes: &l.Routes,
|
||||
CommercialMode: &l.CommercialMode,
|
||||
PhysicalModes: &l.PhysicalModes,
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Line types : %w", err)
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"Line", b}
|
||||
|
||||
// Now process the values
|
||||
|
||||
// For Color: we expect a color string length of 6 because it should be coded in hexadecimal
|
||||
if str := data.Color; len(str) == 6 {
|
||||
clr, err := parseColor(str)
|
||||
if err != nil {
|
||||
return gen.err(err, "Color", "color", str, "error in parseColor")
|
||||
}
|
||||
l.Color = clr
|
||||
}
|
||||
|
||||
// For OpeningTime and ClosingTime: we define a function to help us
|
||||
parseTime := func(str string) (h, m, s uint8, err error) {
|
||||
if len(str) != 6 {
|
||||
err = errors.Errorf("time string not to standard: len=%d instead of 6", len(str))
|
||||
return
|
||||
}
|
||||
h64, err := strconv.ParseUint(str[:2], 10, 8)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error while parsing hours")
|
||||
return
|
||||
}
|
||||
m64, err := strconv.ParseUint(str[2:4], 10, 8)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error while parsing minutes")
|
||||
return
|
||||
}
|
||||
s64, err := strconv.ParseUint(str[4:], 10, 8)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error while parsing seconds")
|
||||
return
|
||||
}
|
||||
return uint8(h64), uint8(m64), uint8(s64), nil
|
||||
}
|
||||
// We expect as well a 6-character long value
|
||||
if str := data.OpeningTime; len(str) == 6 {
|
||||
t := &l.OpeningTime
|
||||
var err error
|
||||
t.Hours, t.Minutes, t.Seconds, err = parseTime(str)
|
||||
if err != nil {
|
||||
return gen.err(err, "OpeningTime", "opening_time", str, "error in parseTime")
|
||||
}
|
||||
}
|
||||
if str := data.ClosingTime; len(str) == 6 {
|
||||
t := &l.ClosingTime
|
||||
var err error
|
||||
t.Hours, t.Minutes, t.Seconds, err = parseTime(str)
|
||||
if err != nil {
|
||||
return gen.err(err, "ClosingTime", "closing_time", str, "error in parseTime")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
44
types/line_test.go
Normal file
44
types/line_test.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Line_Unmarshal tests unmarshalling for Line.
|
||||
// As the unmarshalling is done in-house, this allows us to check that the custom UnmarshalJSON function correctly
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func Test_Line_Unmarshal(t *testing.T) {
|
||||
testUnmarshal(t, testData["line"], reflect.TypeOf(Line{}))
|
||||
}
|
||||
|
||||
// BenchmarkLineUnmarshal benchmarks Line unmarshalling via subbenchmarks
|
||||
func BenchmarkLineUnmarshal(b *testing.B) {
|
||||
// Get the bench data
|
||||
data := testData["line"].bench
|
||||
if len(data) == 0 {
|
||||
b.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Run function generator, allowing parallel run
|
||||
runGen := func(in []byte) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l := &Line{}
|
||||
_ = l.UnmarshalJSON(in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all corpus
|
||||
for name, datum := range data {
|
||||
// Get run function
|
||||
runFunc := runGen(datum)
|
||||
|
||||
// Run it !
|
||||
b.Run(name, runFunc)
|
||||
}
|
||||
}
|
||||
11
types/linereport.go
Normal file
11
types/linereport.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package types
|
||||
|
||||
type LineReports struct {
|
||||
Disruptions []Disruption `json:"disruptions"`
|
||||
LineReports []LineReport `json:"line_reports"`
|
||||
}
|
||||
|
||||
type LineReport struct {
|
||||
Line Line `json:"line"`
|
||||
PTObjects []PTObject `json:"pt_objects"`
|
||||
}
|
||||
8
types/link.go
Normal file
8
types/link.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package types
|
||||
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Type string `json:"type"`
|
||||
Rel string `json:"rel"`
|
||||
Templated bool `json:"templated"`
|
||||
}
|
||||
7
types/message.go
Normal file
7
types/message.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package types
|
||||
|
||||
// A Message contains the text to be provided to the traveler.
|
||||
type Message struct {
|
||||
Text string `json:"text"` // The message to bring to the traveler
|
||||
Channel *Channel `json:"channel"` // The destination media for this Message.
|
||||
}
|
||||
70
types/misc.go
Normal file
70
types/misc.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DataFreshness codes for a specific data freshness requirement: realtime or base_schedule
|
||||
type DataFreshness string
|
||||
|
||||
// parseColor, given a hex code #RRGGBB returns a color.NRGBA
|
||||
func parseColor(str string) (color.NRGBA, error) {
|
||||
var clr color.NRGBA
|
||||
|
||||
if len(str) != 6 {
|
||||
return clr, errors.Errorf("parseColor: can't parse, invalid length (len=%d instead of 6)", len(str))
|
||||
}
|
||||
|
||||
r, err := strconv.ParseUint(str[:2], 16, 8)
|
||||
if err != nil {
|
||||
return clr, errors.Wrapf(err, "parseColor: red component parsing failed (str: %s)", str[:2])
|
||||
}
|
||||
g, err := strconv.ParseUint(str[2:4], 16, 8)
|
||||
if err != nil {
|
||||
return clr, errors.Wrapf(err, "parseColor: green component parsing failed (str: %s)", str[2:4])
|
||||
}
|
||||
b, err := strconv.ParseUint(str[4:], 16, 8)
|
||||
if err != nil {
|
||||
return clr, errors.Wrapf(err, "parseColor: blue component parsing failed (str: %s)", str[4:])
|
||||
}
|
||||
|
||||
return color.NRGBA{
|
||||
R: uint8(r),
|
||||
G: uint8(g),
|
||||
B: uint8(b),
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
// DataFreshnessRealTime means you'll get undisrupted journeys
|
||||
DataFreshnessRealTime DataFreshness = "realtime"
|
||||
// DataFreshnessBaseSchedule means you can get disrupted journeys in the response.
|
||||
DataFreshnessBaseSchedule = "base_schedule"
|
||||
)
|
||||
|
||||
// A PTDateTime (pt stands for “public transport”) is a complex date time object to manage the difference between stop and leaving times at a stop.
|
||||
// It is used by:
|
||||
// - Row in Schedule
|
||||
// - StopSchedule
|
||||
// - StopDatetime
|
||||
type PTDateTime struct {
|
||||
// Date/Time of departure
|
||||
Departure time.Time `json:"departure"`
|
||||
|
||||
// Date/Time of arrival
|
||||
Arrival time.Time `json:"arrival"`
|
||||
}
|
||||
|
||||
// A Code is associated to a dataset
|
||||
//
|
||||
// Every object managed by Navitia comes with its own list of ids.
|
||||
// You will find some source ids, merge ids, etc. in “codes” list in json responses.
|
||||
// Be careful, these codes may not be unique. The navitia id is the only unique id.
|
||||
type Code struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
66
types/mode.go
Normal file
66
types/mode.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package types
|
||||
|
||||
// ModeXXX are known non-public transportation mode
|
||||
const (
|
||||
ModeWalking = "walking"
|
||||
ModeBike = "bike"
|
||||
ModeCar = "car"
|
||||
|
||||
// Not used in Section
|
||||
ModeBikeShare = "bss"
|
||||
)
|
||||
|
||||
// A CommercialMode codes for a commercial method of transportation.
|
||||
//
|
||||
// Note that in contrast with physical modes, commercial modes aren't normalised, if you want to query with them, it is best to use a PhysicalMode.
|
||||
//
|
||||
// See http://doc.navitia.io/#public-transport-objects
|
||||
type CommercialMode struct {
|
||||
// A CommercialMode ID is in the form of "commercial_mode:something"
|
||||
ID ID `json:"id"`
|
||||
|
||||
// Name of the commercial mode
|
||||
Name string `json:"name"`
|
||||
|
||||
// Physical modes of this commercial modes
|
||||
// Example: []PhysicalMode{PhysicalMode{ID: "physical_mode:Tramway", Name: "Tramway"}}
|
||||
PhysicalModes []PhysicalMode `json:"physical_modes"`
|
||||
}
|
||||
|
||||
// A PhysicalMode codes for a physical method of transportation
|
||||
// For example, air travel, bus, metro and train.
|
||||
//
|
||||
// As well, note that physical modes are normalised and fastened, see the list in PhysicalModes
|
||||
//
|
||||
// See http://doc.navitia.io/#public-transport-objects
|
||||
type PhysicalMode struct {
|
||||
// Identifier of the physical mode
|
||||
// For example: "physical_mode:Tramway"
|
||||
ID ID `json:"id"`
|
||||
|
||||
// Name of the physical mode
|
||||
// For example: "Tramway"
|
||||
Name string `json:"name"`
|
||||
|
||||
// Commercial modes of this physical mode
|
||||
CommercialModes []CommercialMode `json:"commercial_mode"`
|
||||
}
|
||||
|
||||
// PhysicalModeXXX are the possible physical modes in ID form
|
||||
const (
|
||||
PhysicalModeAir ID = "physical_mode:Air"
|
||||
PhysicalModeBoat ID = "physical_mode:Boat"
|
||||
PhysicalModeBus ID = "physical_mode:Bus"
|
||||
PhysicalModeBusRapidTransit ID = "physical_mode:BusRapidTransit"
|
||||
PhysicalModeCoach ID = "physical_mode:Coach"
|
||||
PhysicalModeFerry ID = "physical_mode:Ferry"
|
||||
PhysicalModeFunicular ID = "physical_mode:Funicular"
|
||||
PhysicalModeLocalTrain ID = "physical_mode:LocalTrain"
|
||||
PhysicalModeLongDistanceTrain ID = "physical_mode:LongDistanceTrain"
|
||||
PhysicalModeMetro ID = "physical_mode:Metro"
|
||||
PhysicalModeRapidTransit ID = "physical_mode:RapidTransit"
|
||||
PhysicalModeShuttle ID = "physical_mode:Shuttle"
|
||||
PhysicalModeTaxi ID = "physical_mode:Taxi"
|
||||
PhysicalModeTrain ID = "physical_mode:Train"
|
||||
PhysicalModeTramway ID = "physical_mode:Tramway"
|
||||
)
|
||||
9
types/network.go
Normal file
9
types/network.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package types
|
||||
|
||||
// Network represents a specific network.
|
||||
// They are fed by the agencies in GTFS format.
|
||||
// See http://doc.navitia.io/#public-transport-objects.
|
||||
type Network struct {
|
||||
ID string `json:"id"` // ID is the identifier of the network
|
||||
Name string `json:"name"` // Name is the name of the network
|
||||
}
|
||||
54
types/pathSegment.go
Normal file
54
types/pathSegment.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A PathSegment (called Path item in the Navitia API) is a part of a path
|
||||
type PathSegment struct {
|
||||
Length uint `json:"length"` // The Length of the segment
|
||||
Name string `json:"name"` // The Name of the way corresponding to the segment
|
||||
Duration time.Duration `json:"duration"` // The duration in seconds of the segment
|
||||
|
||||
// The angle in degree between the previous segment and this segment
|
||||
// = 0 Means going straight
|
||||
// < 0 Means turning left
|
||||
// > 0 Means turning right
|
||||
Direction int `json:"direction"`
|
||||
}
|
||||
|
||||
// jsonPathSegment define the JSON implementation of PathSegment types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonPathSegment struct {
|
||||
// Pointers to the corresponding real values
|
||||
Length *uint
|
||||
Name *string
|
||||
Direction *int
|
||||
|
||||
// Value to process
|
||||
Duration int64
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a PathSegment
|
||||
func (ps *PathSegment) UnmarshalJSON(b []byte) error {
|
||||
data := &jsonPathSegment{
|
||||
Length: &ps.Length,
|
||||
Name: &ps.Name,
|
||||
Direction: &ps.Direction,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling PathSegment: %w", err)
|
||||
}
|
||||
|
||||
// Now process the value
|
||||
// As the given duration is in second, let's multiply it by one second to have the correct value
|
||||
ps.Duration = time.Duration(data.Duration) * time.Second
|
||||
|
||||
return nil
|
||||
}
|
||||
47
types/period.go
Normal file
47
types/period.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Period of effect
|
||||
type Period struct {
|
||||
Begin time.Time `json:"begin"`
|
||||
End time.Time `json:"end"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Period
|
||||
func (p *Period) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
// We define some of the value as pointers to the real values, allowing us to bypass copying in cases where we don't need to process the data
|
||||
data := &struct {
|
||||
// Those we will process
|
||||
Begin string `json:"begin"`
|
||||
End string `json:"end"`
|
||||
}{}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"Period", b}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error while unmarshalling Period")
|
||||
}
|
||||
|
||||
// Now we process the times.
|
||||
p.Begin, err = parseDateTime(data.Begin)
|
||||
if err != nil {
|
||||
return gen.err(err, "Begin", "begin", data.Begin, "parseDateTime failed")
|
||||
}
|
||||
p.End, err = parseDateTime(data.End)
|
||||
if err != nil {
|
||||
return gen.err(err, "End", "end", data.End, "parseDateTime failed")
|
||||
}
|
||||
|
||||
// Finished !
|
||||
return nil
|
||||
}
|
||||
128
types/place.go
Normal file
128
types/place.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package types
|
||||
|
||||
// A Place isn't something directly used by the Navitia.io api.
|
||||
//
|
||||
// However, it allows the library user to use idiomatic go when working with the library.
|
||||
// If you want a countainer, see Container
|
||||
//
|
||||
// Place is held by these types:
|
||||
// - StopArea
|
||||
// - POI
|
||||
// - Address
|
||||
// - StopPoint
|
||||
// - Admin
|
||||
type Place interface{}
|
||||
|
||||
// A StopArea represents a stop area: a nameable zone, where there are some stop points.
|
||||
type StopArea struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Label of the stop area.
|
||||
// The name is directly taken from the data whereas the label is something computed by navitia for better traveler information.
|
||||
// If you don't know what to display, display the label
|
||||
Label string `json:"label"`
|
||||
|
||||
// Coordinates of the stop area
|
||||
Coord Coordinates `json:"coord"`
|
||||
|
||||
// Administrative regions of the stop area in which is placed the stop area
|
||||
Admins []Admin `json:"administrative_regions"`
|
||||
|
||||
// Stop points countained in this stop area
|
||||
StopPoints []StopPoint `json:"stop_points"`
|
||||
|
||||
Timezone string `json:"timezone"`
|
||||
}
|
||||
|
||||
// A POIType codes for the type of the point of interest
|
||||
type POIType struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// A POI is a Point Of Interest. A loosely-defined place.
|
||||
type POI struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// The name is directly taken from the data whereas the label is something computed by navitia for better traveler information.
|
||||
// If you don't know what to display, display the label
|
||||
Label string `json:"label"`
|
||||
|
||||
// The type of the POI
|
||||
Type POIType `json:"poi_type"`
|
||||
}
|
||||
|
||||
// An Address codes for a real-world address: a point located in a street.
|
||||
type Address struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Label of the address
|
||||
// The name is directly taken from the data whereas the label is something computed by navitia for better traveler information.
|
||||
// If you don't know what to display, display the label
|
||||
Label string `json:"label"`
|
||||
|
||||
// Coordinates of the address
|
||||
Coord Coordinates `json:"coord"`
|
||||
|
||||
// House number of the address
|
||||
HouseNumber uint `json:"house_number"`
|
||||
|
||||
// Administrative regions of the stop area in which is placed the stop area
|
||||
Admins []Admin `json:"administrative_regions"`
|
||||
}
|
||||
|
||||
// A StopPoint codes for a stop point in a line: a location where vehicles can pickup or drop off passengers.
|
||||
type StopPoint struct {
|
||||
ID ID `json:"id"`
|
||||
|
||||
// Name of the stop point
|
||||
Name string `json:"name"`
|
||||
|
||||
Label string `json:"label"`
|
||||
|
||||
// Coordinates of the stop point
|
||||
Coord Coordinates `json:"coord"`
|
||||
|
||||
// Administrative regions of the stop point
|
||||
Admins []Admin `json:"administrative_regions"`
|
||||
|
||||
// List of equipments of the stop point
|
||||
Equipments []Equipment `json:"equipment"`
|
||||
|
||||
// Stop Area countaining the stop point
|
||||
StopArea *StopArea `json:"stop_area"`
|
||||
|
||||
CommercialModes []CommercialMode `json:"commercial_modes"`
|
||||
|
||||
Links []Link `json:"links"`
|
||||
|
||||
PhysicalModes []PhysicalMode `json:"physical_modes"`
|
||||
|
||||
FareZone FareZone `json:"fare_zone"`
|
||||
}
|
||||
|
||||
// An Admin represents an administrative region: a region under the control/responsibility of a specific organisation.
|
||||
// It can be a city, a district, a neightborhood, etc.
|
||||
type Admin struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Label of the address
|
||||
// The name is directly taken from the data whereas the label is something computed by navitia for better traveler information.
|
||||
// If you don't know what to display, display the label
|
||||
Label string `json:"label"`
|
||||
|
||||
// Coordinates of the administrative region
|
||||
Coord Coordinates `json:"coord"`
|
||||
|
||||
// Level of the administrative region
|
||||
Level int `json:"level"`
|
||||
|
||||
// Zip code of the administrative region
|
||||
ZipCode string `json:"zip_code"`
|
||||
|
||||
Insee string `json:"insee"`
|
||||
}
|
||||
12
types/ptobject.go
Normal file
12
types/ptobject.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package types
|
||||
|
||||
// A PTObject is a Public Transport object: StopArea, Trip, Line, Route, Network, etc.
|
||||
type PTObject interface{}
|
||||
|
||||
// A Trip corresponds to a scheduled vehicle circulation (and all its linked real-time and disrupted routes).
|
||||
//
|
||||
// An example : a train, routing a Paris to Lyon itinerary every day at 06h29, is the “Trip” named “6641”.
|
||||
type Trip struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
149
types/region.go
Normal file
149
types/region.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mb0/wkt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/twpayne/go-geom"
|
||||
)
|
||||
|
||||
// A Region holds information about a geographical region, including its ID, name & shape.
|
||||
type Region struct {
|
||||
ID ID `json:"id"` // Identifier of the region
|
||||
Name string `json:"name"` // Name of the region
|
||||
Status string `json:"status"` // Status of the dataset
|
||||
|
||||
// Shape of the region.
|
||||
// You can use it to check if a particular coordinate is within that MultiPolygon
|
||||
Shape *geom.MultiPolygon `json:"shape"`
|
||||
|
||||
DatasetCreation time.Time `json:"dataset_creation"` // When was the DataSet created ?
|
||||
LastLoaded time.Time `json:"last_loaded"` // When was it last loaded at navitia.io's end ?
|
||||
ProductionStart time.Time `json:"production_start"` // When did production start ?
|
||||
ProductionEnd time.Time `json:"production_end"` // When did or when will it stop ?
|
||||
|
||||
// An error in the dataset.
|
||||
// This comes from the server, not from this package.
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// jsonRegion define the JSON implementation of Region types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonRegion struct {
|
||||
ID *ID `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Status *string `json:"status"`
|
||||
|
||||
// This is mind-fuckery of the highest level.
|
||||
// While EVERY other geojson value returned by navitia is in standard format, THIS ONE, for NO GOOD REASON is coded in wkt...
|
||||
// See (http://en.wikipedia.org/wiki/Well-known_text).
|
||||
Shape string `json:"shape"`
|
||||
|
||||
DatasetCreation string `json:"dataset_created_at"`
|
||||
LastLoaded string `json:"last_load_at"`
|
||||
|
||||
ProductionStart string `json:"start_production_date"`
|
||||
ProductionEnd string `json:"end_production_date"`
|
||||
|
||||
Error *string `json:"error"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Region
|
||||
func (r *Region) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
data := &jsonRegion{
|
||||
ID: &r.ID,
|
||||
Name: &r.Name,
|
||||
Status: &r.Status,
|
||||
Error: &r.Error,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Region: %w", err)
|
||||
}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"Region", b}
|
||||
|
||||
// Now let's process the values
|
||||
// First the times
|
||||
r.DatasetCreation, err = parseDateTime(data.DatasetCreation)
|
||||
if err != nil {
|
||||
return gen.err(err, "DatasetCreation", "dataset_created_at", data.DatasetCreation, "Error while parsing datetime")
|
||||
}
|
||||
r.LastLoaded, err = parseDateTime(data.LastLoaded)
|
||||
if err != nil {
|
||||
return gen.err(err, "LastLoaded", "last_load_at", data.LastLoaded, "Error while parsing datetime")
|
||||
}
|
||||
r.ProductionStart, err = parseDateTime(data.ProductionStart)
|
||||
if err != nil {
|
||||
return gen.err(err, "ProductionStart", "start_production_date", data.ProductionStart, "Error while parsing datetime")
|
||||
}
|
||||
r.ProductionEnd, err = parseDateTime(data.ProductionEnd)
|
||||
if err != nil {
|
||||
return gen.err(err, "ProductionEnd", "end_production_date", data.ProductionEnd, "Error while parsing datetime")
|
||||
}
|
||||
|
||||
// And now let's have some FUN, deal with the "shape" key.
|
||||
// First, let's check if the string isn't empty, cause that would be so awesome...
|
||||
if data.Shape != "" {
|
||||
// Parse the MKT
|
||||
out, err := wkt.Parse([]byte(data.Shape))
|
||||
if err != nil {
|
||||
return gen.err(err, "Shape", "shape", out, "error in wkt.Parse")
|
||||
}
|
||||
|
||||
// Now, out should be a wkt.MultiPolygon
|
||||
wktmp, ok := out.(*wkt.MultiPolygon)
|
||||
if !ok {
|
||||
return gen.err(nil, "Shape", "shape", out, "expected out to be of type wkt.MultiPolygon, but it isn't !")
|
||||
}
|
||||
|
||||
// Call our funny little function to convert that to a geom format
|
||||
mp, err := convertWktMPtoGeomMP(wktmp)
|
||||
if err != nil {
|
||||
return gen.err(err, "Shape", "shape", wktmp, "error while converting *wkt.MultiPolygon to *geom.MultiPolygon via convertWktMPtoGeomMP")
|
||||
}
|
||||
|
||||
r.Shape = mp
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertWktMPtoGeomMP converts a wkt MultiPolygon to a geom MultiPolygon
|
||||
func convertWktMPtoGeomMP(in *wkt.MultiPolygon) (*geom.MultiPolygon, error) {
|
||||
// Now let's convert it to a geom format
|
||||
// First let's create the geom.MultiPolygon
|
||||
mp := geom.NewMultiPolygon(geom.XY)
|
||||
|
||||
// Then let's iterate through the polygons, and convert each of them from wkt.Coord to geom.Coord
|
||||
multipolygonCoords := make([][][]geom.Coord, len(in.Polygons))
|
||||
for i, k := range in.Polygons {
|
||||
polygonCoords := make([][]geom.Coord, len(k))
|
||||
for j, l := range k {
|
||||
coords := make([]geom.Coord, len(l))
|
||||
for n, m := range l {
|
||||
coord := make(geom.Coord, 2)
|
||||
coord[0] = m.X
|
||||
coord[1] = m.Y
|
||||
coords[n] = coord
|
||||
}
|
||||
polygonCoords[j] = coords
|
||||
}
|
||||
multipolygonCoords[i] = polygonCoords
|
||||
}
|
||||
|
||||
// Now assign it !
|
||||
mp, err := mp.SetCoords(multipolygonCoords)
|
||||
if err != nil {
|
||||
return mp, errors.Wrapf(err, "Error while setting coordinates")
|
||||
}
|
||||
return mp, err
|
||||
}
|
||||
15
types/region_fuzz.go
Normal file
15
types/region_fuzz.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// +build gofuzz
|
||||
|
||||
package types
|
||||
|
||||
func FuzzRegion(data []byte) int {
|
||||
r := &Region{}
|
||||
|
||||
// Let's unmarshal
|
||||
err := r.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
71
types/region_test.go
Normal file
71
types/region_test.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Region_Unmarshal tests unmarshalling for Region.
|
||||
// As the unmarshalling is done in-house, this allows us to check that the custom UnmarshalJSON function correctly
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func Test_Region_Unmarshal(t *testing.T) {
|
||||
testUnmarshal(t, testData["region"], reflect.TypeOf(Region{}))
|
||||
}
|
||||
|
||||
// TestRegionUnmarshal_ShapeInvalidMKT tests known invalid MKT (well-known text) -encoded Region.Shape inputs for (*Region).UnmarshalJSON
|
||||
func TestRegionUnmarshal_ShapeInvalidMKT(t *testing.T) {
|
||||
// Shapes
|
||||
shapes := [...]string{
|
||||
"MULTIPOLYGON(((-11.33535 51.29165,0,0,-11.33535 51.29165))",
|
||||
"MULTIPOLYGON((",
|
||||
"MULTIPOLYGON(((-11.33535 51.29165,0,0,-11.33535 51.29165)",
|
||||
"MULTIPOLYGON(",
|
||||
"POLYGON(",
|
||||
"MULTIPOLYGON(((0",
|
||||
}
|
||||
|
||||
// Run
|
||||
for i, s := range shapes {
|
||||
in := []byte(fmt.Sprintf(`{"shape": "%s"}`, s))
|
||||
r := &Region{}
|
||||
err := r.UnmarshalJSON(in)
|
||||
if err == nil {
|
||||
t.Errorf("No error in run #%d even though we expected one", i)
|
||||
} else if !strings.Contains(err.Error(), "EOF") {
|
||||
t.Errorf("Unexpected error in run #%d with [%s]: %v", i, in, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkRegionUnmarshal benchmarks Region unmarshalling via subbenchmarks
|
||||
func BenchmarkRegionUnmarshal(b *testing.B) {
|
||||
// Get the bench data
|
||||
data := testData["region"].bench
|
||||
if len(data) == 0 {
|
||||
b.Skip("No data to test")
|
||||
}
|
||||
|
||||
// Run function generator, allowing parallel run
|
||||
runGen := func(in []byte) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
r := &Region{}
|
||||
_ = r.UnmarshalJSON(in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop over all corpus
|
||||
for name, datum := range data {
|
||||
// Get run function
|
||||
runFunc := runGen(datum)
|
||||
|
||||
// Run it !
|
||||
b.Run(name, runFunc)
|
||||
}
|
||||
}
|
||||
67
types/route.go
Normal file
67
types/route.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A Route represents a route: a Line can have several routes,
|
||||
// that is several directions with potential junctions and different frequency for each.
|
||||
// See http://doc.navitia.io/#public-transport-objects.
|
||||
type Route struct {
|
||||
ID ID `json:"id"` // Identifier of the route, eg: "route:RAT:M6"
|
||||
Name string `json:"name"` // Name of the route
|
||||
Frequence bool `json:"is_frequence"` // If the route has frequency or not. Can only be “False”, but may be “True” in the future
|
||||
Line Line `json:"line"` // Line is the line it is connected to
|
||||
Direction Container `json:"direction"` // Direction is the direction of the route (Place or POI)
|
||||
PhysicalModes []PhysicalMode `json:"physical_modes"` // PhysicalModes of the line
|
||||
GeoJSON GeoJSON `json:"geo_json"`
|
||||
}
|
||||
|
||||
// jsonRoute define the JSON implementation of Route types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonRoute struct {
|
||||
ID *ID `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
Line *Line `json:"line"`
|
||||
Direction *Container `json:"direction"`
|
||||
|
||||
// Value to process
|
||||
Frequence string `json:"is_frequence"`
|
||||
}
|
||||
|
||||
type GeoJSON struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for Route
|
||||
func (r *Route) UnmarshalJSON(b []byte) error {
|
||||
data := &jsonRoute{
|
||||
ID: &r.ID,
|
||||
Name: &r.Name,
|
||||
Line: &r.Line,
|
||||
Direction: &r.Direction,
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"Route", b}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Line")
|
||||
}
|
||||
|
||||
// Now process the value
|
||||
switch {
|
||||
case data.Frequence == "true" || data.Frequence == "True":
|
||||
r.Frequence = true
|
||||
case data.Frequence == "false" || data.Frequence == "False":
|
||||
r.Frequence = false
|
||||
default:
|
||||
return gen.err(nil, "Frequence", "is_frequency", data.Frequence, `String is neither True, true, False or false`)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
types/route_test.go
Normal file
16
types/route_test.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Route_Unmarshal tests unmarshalling for Route.
|
||||
// As the unmarshalling is done in-house, this allows us to check that the custom UnmarshalJSON function correctly
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func Test_Route_Unmarshal(t *testing.T) {
|
||||
testUnmarshal(t, testData["route"], reflect.TypeOf(Route{}))
|
||||
}
|
||||
235
types/section.go
Normal file
235
types/section.go
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/twpayne/go-geom"
|
||||
"github.com/twpayne/go-geom/encoding/geojson"
|
||||
)
|
||||
|
||||
// A Section holds information about a specific section
|
||||
type Section struct {
|
||||
Type SectionType
|
||||
ID ID
|
||||
Mode string
|
||||
From Container
|
||||
To Container
|
||||
Departure time.Time // Departure time
|
||||
Arrival time.Time // Arrival time
|
||||
Duration time.Duration // Duration of travel
|
||||
Path []PathSegment // The path taken by this section
|
||||
Geo *geom.LineString // The path in geojson format
|
||||
StopTimes []StopTime // List of the stop times of this section
|
||||
Display Display // Information to display
|
||||
Additional []PTMethod // Additional informations, from what I can see this is always a PTMethod
|
||||
}
|
||||
|
||||
// jsonSection define the JSON implementation of Section types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonSection struct {
|
||||
// Pointers to the corresponding real values
|
||||
Type *SectionType `json:"type"`
|
||||
ID *ID `json:"id"`
|
||||
From *Container `json:"from"`
|
||||
To *Container `json:"to"`
|
||||
Mode *string `json:"mode"`
|
||||
StopTimes *[]StopTime `json:"stop_date_times"`
|
||||
Display *Display `json:"display_informations"`
|
||||
Additional *[]PTMethod `json:"additional_informations"`
|
||||
Path *[]PathSegment `json:"path"`
|
||||
|
||||
// Values to process
|
||||
Departure string `json:"departure_date_time"`
|
||||
Arrival string `json:"arrival_date_time"`
|
||||
Duration int64 `json:"duration"`
|
||||
Geo *geojson.Geometry `json:"geojson"`
|
||||
}
|
||||
|
||||
// A SectionType codifies the type of section that can be encountered
|
||||
type SectionType string
|
||||
|
||||
// These are the types of sections that can be returned from the API
|
||||
const (
|
||||
// Public transport section
|
||||
SectionPublicTransport SectionType = "public_transport"
|
||||
|
||||
// Street section
|
||||
SectionStreetNetwork SectionType = "street_network"
|
||||
|
||||
// Waiting section between transport
|
||||
SectionWaiting SectionType = "waiting"
|
||||
|
||||
// This “stay in the vehicle” section occurs when the traveller has to stay in the vehicle when the bus change its routing.
|
||||
SectionStayIn SectionType = "stay_in"
|
||||
|
||||
// Transfer section
|
||||
SectionTransfer SectionType = "transfer"
|
||||
|
||||
// Teleportation section. Used when starting or arriving to a city or a stoparea (“potato shaped” objects) Useful to make navitia idempotent.
|
||||
// Warning: Be careful: no Path nor Geo items in this case
|
||||
SectionCrowFly SectionType = "crow_fly"
|
||||
|
||||
// Vehicle may not drive along: traveler will have to call agency to confirm journey
|
||||
// Also sometimes called ODT
|
||||
SectionOnDemandTransport SectionType = "on_demand_transport"
|
||||
|
||||
// Taking a bike from a bike sharing system (bss)
|
||||
SectionBikeShareRent SectionType = "bss_rent"
|
||||
|
||||
// Putting back a bike from a bike sharing system (bss)
|
||||
SectionBikeSharePutBack SectionType = "bss_put_back"
|
||||
|
||||
// Boarding on plane
|
||||
SectionBoarding SectionType = "boarding"
|
||||
|
||||
// Landing off the plane
|
||||
SectionLanding SectionType = "landing"
|
||||
)
|
||||
|
||||
// SectionTypes is the type of a section
|
||||
var SectionTypes = map[SectionType]string{
|
||||
SectionPublicTransport: "Public transport section",
|
||||
SectionStreetNetwork: "Street section",
|
||||
SectionWaiting: "Waiting section between transport",
|
||||
SectionStayIn: "This “stay in the vehicle” section occurs when the traveller has to stay in the vehicle when the bus change its routing.",
|
||||
SectionTransfer: "Transfer section",
|
||||
SectionCrowFly: "Teleportation section. Used when starting or arriving to a city or a stoparea (“potato shaped” objects) Useful to make navitia idempotent",
|
||||
SectionOnDemandTransport: "Vehicle may not drive along: traveler will have to call agency to confirm journey",
|
||||
SectionBikeShareRent: "Taking a bike from a bike sharing system (bss)",
|
||||
SectionBikeSharePutBack: "Putting back a bike from a bike sharing system (bss)",
|
||||
SectionBoarding: "Boarding on plane",
|
||||
SectionLanding: "Landing off the plane",
|
||||
}
|
||||
|
||||
// A StopTime stores info about a stop in a route: when the vehicle comes in, when it comes out, and what stop it is.
|
||||
type StopTime struct {
|
||||
// The PTDateTime of the stop, this stores the info about the arrival & departure
|
||||
PTDateTime PTDateTime
|
||||
StopPoint StopPoint `json:"stop_point"` // The stop point in question
|
||||
DropOffAllowed bool `json:"drop_off_allowed"`
|
||||
UTCDepartureTime string `json:"utc_departure_time"`
|
||||
Headsign string `json:"headsign"`
|
||||
UTCArrivalTime string `json:"utc_arrival_time"`
|
||||
PickupAllowed bool `json:"pickup_allowed"`
|
||||
DepartureTime string `json:"departure_time"`
|
||||
}
|
||||
|
||||
// A PTMethod is a Public Transportation method: it can be regular, estimated times or ODT (on-demand transport)
|
||||
type PTMethod string
|
||||
|
||||
// PTMethodXXX codes for known PTMethod
|
||||
const (
|
||||
// PTMethodRegular: No on-demand transport. Line does not contain any estimated stop times, nor zonal stop point location. No need to call too.
|
||||
PTMethodRegular PTMethod = "regular"
|
||||
|
||||
// PTMethodDateTimeEstimated: No on-demand transport. However, line has at least one estimated date time.
|
||||
PTMethodDateTimeEstimated PTMethod = "had_date_time_estimated"
|
||||
|
||||
// PTMethodODTStopTime: Line does not contain any estimated stop times, nor zonal stop point location. But you will have to call to take it.
|
||||
PTMethodODTStopTime PTMethod = "odt_with_stop_time"
|
||||
|
||||
// PTMethodODTStopPoint: Line can contain some estimated stop times, but no zonal stop point location. And you will have to call to take it.
|
||||
PTMethodODTStopPoint PTMethod = "odt_with_stop_point"
|
||||
|
||||
// PTMethodODTZone: Line can contain some estimated stop times, and zonal stop point location. And you will have to call to take it. Well, not really a public transport line, more a cab…
|
||||
PTMethodODTZone PTMethod = "odt_with_zone"
|
||||
)
|
||||
|
||||
/*
|
||||
UnmarshalJSON implements json.Unmarshaller for a Section
|
||||
|
||||
Behaviour:
|
||||
- If "from" is empty, then don't populate the From field.
|
||||
- Same for "to"
|
||||
*/
|
||||
func (s *Section) UnmarshalJSON(b []byte) error {
|
||||
data := &jsonSection{
|
||||
Type: &s.Type,
|
||||
ID: &s.ID,
|
||||
From: &s.From,
|
||||
To: &s.To,
|
||||
Mode: &s.Mode,
|
||||
Display: &s.Display,
|
||||
Additional: &s.Additional,
|
||||
StopTimes: &s.StopTimes,
|
||||
Path: &s.Path,
|
||||
}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Section: %w", err)
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"Section", b}
|
||||
|
||||
// For departure and arrival, we use parseDateTime
|
||||
s.Departure, err = parseDateTime(data.Departure)
|
||||
if err != nil {
|
||||
return gen.err(err, "Departure", "departure_date_time", data.Departure, "parseDateTime failed")
|
||||
}
|
||||
s.Arrival, err = parseDateTime(data.Arrival)
|
||||
if err != nil {
|
||||
return gen.err(err, "Arrival", "arrival_date_time", data.Arrival, "parseDateTime failed")
|
||||
}
|
||||
|
||||
// As the given duration is in second, let's multiply it by one second to have the correct value
|
||||
s.Duration = time.Duration(data.Duration) * time.Second
|
||||
|
||||
// Now let's deal with the geom
|
||||
if data.Geo != nil {
|
||||
// Catch an error !
|
||||
if data.Geo.Coordinates == nil {
|
||||
return gen.err(nil, "Geo", "geojson", data.Geo, "Geo.Coordinates is nil, can't continue as that will cause a panic")
|
||||
}
|
||||
|
||||
// Let's decode it
|
||||
geot, err := data.Geo.Decode()
|
||||
if err != nil {
|
||||
return gen.err(err, "Geo", "geojson", data.Geo, "Geo.Decode() failed")
|
||||
}
|
||||
// And let's assert the type
|
||||
geo, ok := geot.(*geom.LineString)
|
||||
if !ok {
|
||||
return gen.err(err, "Geo", "geojson", data.Geo, "Geo type assertion failed!")
|
||||
}
|
||||
// Now let's assign it
|
||||
s.Geo = geo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a PTDateTime
|
||||
func (ptdt *PTDateTime) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
data := &struct {
|
||||
Departure string `json:"departure_date_time"`
|
||||
Arrival string `json:"arrival_date_time"`
|
||||
}{}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling PTDateTime: %w", err)
|
||||
}
|
||||
|
||||
// Create the error generator
|
||||
gen := unmarshalErrorMaker{"PTDateTime", b}
|
||||
|
||||
// Now we use parseDateTime
|
||||
ptdt.Departure, err = parseDateTime(data.Departure)
|
||||
if err != nil {
|
||||
return gen.err(err, "Departure", "departure_date_time", data.Departure, "parseDateTime failed")
|
||||
}
|
||||
ptdt.Arrival, err = parseDateTime(data.Arrival)
|
||||
if err != nil {
|
||||
return gen.err(err, "Arrival", "arrival_date_time", data.Arrival, "parseDateTime failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
types/section_test.go
Normal file
16
types/section_test.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test_Section_Unmarshal tests unmarshalling for Section.
|
||||
// As the unmarshalling is done in-house, this allows us to check that the custom UnmarshalJSON function correctly
|
||||
//
|
||||
// This launches both a "correct" and "incorrect" subtest, allowing us to test both cases.
|
||||
// If we expect no errors but we get one, the test fails
|
||||
// If we expect an error but we don't get one, the test fails
|
||||
func Test_Section_Unmarshal(t *testing.T) {
|
||||
testUnmarshal(t, testData["section"], reflect.TypeOf(Section{}))
|
||||
}
|
||||
66
types/severity.go
Normal file
66
types/severity.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Severity object can be used to make visual grouping.
|
||||
type Severity struct {
|
||||
// Name of severity
|
||||
Name string `json:"name"`
|
||||
|
||||
// Priority of the severity. Given by the agency. 0 is the strongest priority, a nil Priority means its undefined (duh).
|
||||
Priority *int `json:"priority"`
|
||||
|
||||
// HTML color for classification
|
||||
Color color.Color `json:"color"`
|
||||
|
||||
// Effect: Normalized value of the effect on the public transport object
|
||||
Effect Effect `json:"effect"`
|
||||
}
|
||||
|
||||
// jsonSeverity define the JSON implementation of Severity types
|
||||
// We define some of the value as pointers to the real values,
|
||||
// allowing us to bypass copying in cases where we don't need to process the data.
|
||||
type jsonSeverity struct {
|
||||
// The references
|
||||
Name *string `json:"name"`
|
||||
Priority *int `json:"priority,omitempty"` // As priority can be null, and 0 is the highest priority.
|
||||
Effect *Effect `json:"effect"`
|
||||
|
||||
// Those we will process
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller for a Severity
|
||||
func (s *Severity) UnmarshalJSON(b []byte) error {
|
||||
// First let's create the analogous structure
|
||||
// We define some of the value as pointers to the real values, allowing us to bypass copying in cases where we don't need to process the data
|
||||
data := &jsonSeverity{
|
||||
Name: &s.Name,
|
||||
Priority: s.Priority,
|
||||
Effect: &s.Effect,
|
||||
}
|
||||
|
||||
// Let's create the error generator
|
||||
gen := unmarshalErrorMaker{"Severity", b}
|
||||
|
||||
// Now unmarshall the raw data into the analogous structure
|
||||
err := json.Unmarshal(b, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while unmarshalling Severity: %w", err)
|
||||
}
|
||||
|
||||
// Process the color
|
||||
if str := data.Color; len(str) == 6 {
|
||||
clr, err := parseColor(str)
|
||||
if err != nil {
|
||||
return gen.err(err, "Color", "color", str, "error in parseColor")
|
||||
}
|
||||
s.Color = clr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
41
types/testdata/container/bench/regular.json
vendored
Normal file
41
types/testdata/container/bench/regular.json
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"embedded_type": "stop_area",
|
||||
"quality": 70,
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "RATRDBAC"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "RDBAC"
|
||||
}
|
||||
],
|
||||
"name": "Rue du Bac",
|
||||
"links": [],
|
||||
"coord": {
|
||||
"lat": "48.855756",
|
||||
"lon": "2.325569"
|
||||
},
|
||||
"label": "Rue du Bac (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"timezone": "Europe\/Paris",
|
||||
"id": "stop_area:RAT:SA:RDBAC"
|
||||
},
|
||||
"name": "Rue du Bac (Paris)",
|
||||
"id": "stop_area:RAT:SA:RDBAC"
|
||||
}
|
||||
41
types/testdata/container/correct/a0.json
vendored
Normal file
41
types/testdata/container/correct/a0.json
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"embedded_type": "stop_area",
|
||||
"quality": 70,
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "RATRDBAC"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "RDBAC"
|
||||
}
|
||||
],
|
||||
"name": "Rue du Bac",
|
||||
"links": [],
|
||||
"coord": {
|
||||
"lat": "48.855756",
|
||||
"lon": "2.325569"
|
||||
},
|
||||
"label": "Rue du Bac (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"timezone": "Europe\/Paris",
|
||||
"id": "stop_area:RAT:SA:RDBAC"
|
||||
},
|
||||
"name": "Rue du Bac (Paris)",
|
||||
"id": "stop_area:RAT:SA:RDBAC"
|
||||
}
|
||||
41
types/testdata/container/correct/a1.json
vendored
Normal file
41
types/testdata/container/correct/a1.json
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"embedded_type": "stop_area",
|
||||
"quality": 60,
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "RATMKFDO"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "MKFDO"
|
||||
}
|
||||
],
|
||||
"name": "Malakoff \u2014 Rue Etienne Dolet",
|
||||
"links": [],
|
||||
"coord": {
|
||||
"lat": "48.814668",
|
||||
"lon": "2.296999"
|
||||
},
|
||||
"label": "Malakoff \u2014 Rue Etienne Dolet (Malakoff)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "92046",
|
||||
"name": "Malakoff",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.817406",
|
||||
"lon": "2.297158"
|
||||
},
|
||||
"label": "Malakoff (92240)",
|
||||
"id": "admin:fr:92046",
|
||||
"zip_code": "92240"
|
||||
}
|
||||
],
|
||||
"timezone": "Europe\/Paris",
|
||||
"id": "stop_area:RAT:SA:MKFDO"
|
||||
},
|
||||
"name": "Malakoff \u2014 Rue Etienne Dolet (Malakoff)",
|
||||
"id": "stop_area:RAT:SA:MKFDO"
|
||||
}
|
||||
51
types/testdata/container/correct/a2.json
vendored
Normal file
51
types/testdata/container/correct/a2.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 80,
|
||||
"id": "poi:n682262148",
|
||||
"name": "Rue Chabanais (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Rue Chabanais",
|
||||
"coord": {
|
||||
"lat": "48.8669921",
|
||||
"lon": "2.3366321"
|
||||
},
|
||||
"label": "Rue Chabanais (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 1,
|
||||
"id": "2.3366321;48.8669921",
|
||||
"name": "Rue Chabanais",
|
||||
"coord": {
|
||||
"lat": "48.8669921",
|
||||
"lon": "2.3366321"
|
||||
},
|
||||
"label": "1 Rue Chabanais (Paris)"
|
||||
},
|
||||
"id": "poi:n682262148",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"name": "Rue Chabanais",
|
||||
"source": "cadastre-dgi-fr source : Direction G\u00e9n\u00e9rale des Imp\u00f4ts - Cadastre. Mise \u00e0 jour : 2010",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "02007",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/a3.json
vendored
Normal file
51
types/testdata/container/correct/a3.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 80,
|
||||
"id": "poi:n639606894",
|
||||
"name": "Rue Moncey (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Rue Moncey",
|
||||
"coord": {
|
||||
"lat": "48.8801859",
|
||||
"lon": "2.3312932"
|
||||
},
|
||||
"label": "Rue Moncey (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 2,
|
||||
"id": "2.3312932;48.8801859",
|
||||
"name": "Rue Moncey",
|
||||
"coord": {
|
||||
"lat": "48.8801859",
|
||||
"lon": "2.3312932"
|
||||
},
|
||||
"label": "2 Rue Moncey (Paris)"
|
||||
},
|
||||
"id": "poi:n639606894",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "N\/A",
|
||||
"name": "Rue Moncey",
|
||||
"wheelchair": "no",
|
||||
"operator": "JCDecaux",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/a4.json
vendored
Normal file
51
types/testdata/container/correct/a4.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 80,
|
||||
"id": "poi:n597860967",
|
||||
"name": "Rue montgallet (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Rue montgallet",
|
||||
"coord": {
|
||||
"lat": "48.844328",
|
||||
"lon": "2.3896931"
|
||||
},
|
||||
"label": "Rue montgallet (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 39,
|
||||
"id": "2.3896931;48.844328",
|
||||
"name": "Rue Montgallet",
|
||||
"coord": {
|
||||
"lat": "48.844328",
|
||||
"lon": "2.3896931"
|
||||
},
|
||||
"label": "39 Rue Montgallet (Paris)"
|
||||
},
|
||||
"id": "poi:n597860967",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "16",
|
||||
"name": "Rue montgallet",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "12013",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/a5.json
vendored
Normal file
51
types/testdata/container/correct/a5.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n439912307",
|
||||
"name": "Hittorf - Rue Hittorf - 75010 Paris (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Hittorf - Rue Hittorf - 75010 Paris",
|
||||
"coord": {
|
||||
"lat": "48.8720809",
|
||||
"lon": "2.3576446"
|
||||
},
|
||||
"label": "Hittorf - Rue Hittorf - 75010 Paris (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 14,
|
||||
"id": "2.3576446;48.8720809",
|
||||
"name": "Rue Hittorf",
|
||||
"coord": {
|
||||
"lat": "48.8720809",
|
||||
"lon": "2.3576446"
|
||||
},
|
||||
"label": "14 Rue Hittorf (Paris)"
|
||||
},
|
||||
"id": "poi:n439912307",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "17",
|
||||
"name": "Hittorf - Rue Hittorf - 75010 Paris",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "10009",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
52
types/testdata/container/correct/a6.json
vendored
Normal file
52
types/testdata/container/correct/a6.json
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n272853107",
|
||||
"name": "Rue de Siam (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Rue de Siam",
|
||||
"coord": {
|
||||
"lat": "48.861679",
|
||||
"lon": "2.2753896"
|
||||
},
|
||||
"label": "Rue de Siam (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 1,
|
||||
"id": "2.2753896;48.861679",
|
||||
"name": "Rue de Siam",
|
||||
"coord": {
|
||||
"lat": "48.861679",
|
||||
"lon": "2.2753896"
|
||||
},
|
||||
"label": "1 Rue de Siam (Paris)"
|
||||
},
|
||||
"id": "poi:n272853107",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "16",
|
||||
"name": "Rue de Siam",
|
||||
"source": "survey",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "16017",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/a7.json
vendored
Normal file
51
types/testdata/container/correct/a7.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n340402115",
|
||||
"name": "Rue des Boulets (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Rue des Boulets",
|
||||
"coord": {
|
||||
"lat": "48.8521875",
|
||||
"lon": "2.3889688"
|
||||
},
|
||||
"label": "Rue des Boulets (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 45,
|
||||
"id": "2.3889688;48.8521875",
|
||||
"name": "Rue des Boulets",
|
||||
"coord": {
|
||||
"lat": "48.8521875",
|
||||
"lon": "2.3889688"
|
||||
},
|
||||
"label": "45 Rue des Boulets (Paris)"
|
||||
},
|
||||
"id": "poi:n340402115",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "23",
|
||||
"name": "Rue des Boulets",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "11009",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/a8.json
vendored
Normal file
51
types/testdata/container/correct/a8.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n272852792",
|
||||
"name": "Rue Fran\u00e7ois Ponsard (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Rue Fran\u00e7ois Ponsard",
|
||||
"coord": {
|
||||
"lat": "48.8583046",
|
||||
"lon": "2.2742742"
|
||||
},
|
||||
"label": "Rue Fran\u00e7ois Ponsard (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 4,
|
||||
"id": "2.2742742;48.8583046",
|
||||
"name": "Chauss\u00e9e de la Muette",
|
||||
"coord": {
|
||||
"lat": "48.8583046",
|
||||
"lon": "2.2742742"
|
||||
},
|
||||
"label": "4 Chauss\u00e9e de la Muette (Paris)"
|
||||
},
|
||||
"id": "poi:n272852792",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "23",
|
||||
"name": "Rue Fran\u00e7ois Ponsard",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "16021",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/a9.json
vendored
Normal file
51
types/testdata/container/correct/a9.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 60,
|
||||
"id": "poi:n439919694",
|
||||
"name": "Beaubourg - 46 Rue Beaubourg - 75003 Paris (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Beaubourg - 46 Rue Beaubourg - 75003 Paris",
|
||||
"coord": {
|
||||
"lat": "48.8610006",
|
||||
"lon": "2.353484"
|
||||
},
|
||||
"label": "Beaubourg - 46 Rue Beaubourg - 75003 Paris (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 26,
|
||||
"id": "2.353484;48.8610006",
|
||||
"name": "Rue Geoffroy l'Angevin",
|
||||
"coord": {
|
||||
"lat": "48.8610006",
|
||||
"lon": "2.353484"
|
||||
},
|
||||
"label": "26 Rue Geoffroy l'Angevin (Paris)"
|
||||
},
|
||||
"id": "poi:n439919694",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "18",
|
||||
"name": "Beaubourg - 46 Rue Beaubourg - 75003 Paris",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "3010",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b0.json
vendored
Normal file
51
types/testdata/container/correct/b0.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 80,
|
||||
"id": "poi:n2025403733",
|
||||
"name": "avenue Marceau (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "avenue Marceau",
|
||||
"coord": {
|
||||
"lat": "48.8652776",
|
||||
"lon": "2.3001217"
|
||||
},
|
||||
"label": "avenue Marceau (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 4,
|
||||
"id": "2.3001217;48.8652776",
|
||||
"name": "Avenue Marceau",
|
||||
"coord": {
|
||||
"lat": "48.8652776",
|
||||
"lon": "2.3001217"
|
||||
},
|
||||
"label": "4 Avenue Marceau (Paris)"
|
||||
},
|
||||
"id": "poi:n2025403733",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "30",
|
||||
"name": "avenue Marceau",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "8046",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b1.json
vendored
Normal file
51
types/testdata/container/correct/b1.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n4037878821",
|
||||
"name": "13, Avenue Foch (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Parking v\u00e9lo",
|
||||
"id": "poi_type:amenity:bicycle_parking"
|
||||
},
|
||||
"name": "13, Avenue Foch",
|
||||
"coord": {
|
||||
"lat": "48.845978",
|
||||
"lon": "2.440875"
|
||||
},
|
||||
"label": "13, Avenue Foch (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 11,
|
||||
"id": "2.440875;48.845978",
|
||||
"name": "Avenue Foch",
|
||||
"coord": {
|
||||
"lat": "48.845978",
|
||||
"lon": "2.440875"
|
||||
},
|
||||
"label": "11 Avenue Foch"
|
||||
},
|
||||
"id": "poi:n4037878821",
|
||||
"properties": {
|
||||
"amenity": "bicycle_parking",
|
||||
"name": "13, Avenue Foch",
|
||||
"access": "permissive",
|
||||
"operator": "10",
|
||||
"covered": "no",
|
||||
"bicycle_parking": "rack"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b2.json
vendored
Normal file
51
types/testdata/container/correct/b2.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n340398579",
|
||||
"name": "Avenue de Gravelle (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Avenue de Gravelle",
|
||||
"coord": {
|
||||
"lat": "48.8243871",
|
||||
"lon": "2.4184684"
|
||||
},
|
||||
"label": "Avenue de Gravelle (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 0,
|
||||
"id": "2.4184684;48.8243871",
|
||||
"name": "Rue du Bac",
|
||||
"coord": {
|
||||
"lat": "48.8243871",
|
||||
"lon": "2.4184684"
|
||||
},
|
||||
"label": "Rue du Bac"
|
||||
},
|
||||
"id": "poi:n340398579",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "50",
|
||||
"name": "Avenue de Gravelle",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "12126",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
52
types/testdata/container/correct/b3.json
vendored
Normal file
52
types/testdata/container/correct/b3.json
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n1490011488",
|
||||
"name": "Avenue des Gobelins (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Avenue des Gobelins",
|
||||
"coord": {
|
||||
"lat": "48.8371959",
|
||||
"lon": "2.3514837"
|
||||
},
|
||||
"label": "Avenue des Gobelins (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 22,
|
||||
"id": "2.3514837;48.8371959",
|
||||
"name": "Avenue des Gobelins",
|
||||
"coord": {
|
||||
"lat": "48.8371959",
|
||||
"lon": "2.3514837"
|
||||
},
|
||||
"label": "22 Avenue des Gobelins (Paris)"
|
||||
},
|
||||
"id": "poi:n1490011488",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "40",
|
||||
"name": "Avenue des Gobelins",
|
||||
"wheelchair": "no",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "0527",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b4.json
vendored
Normal file
51
types/testdata/container/correct/b4.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n489537569",
|
||||
"name": "Avenue des Portugais (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Avenue des Portugais",
|
||||
"coord": {
|
||||
"lat": "48.8711878",
|
||||
"lon": "2.2937312"
|
||||
},
|
||||
"label": "Avenue des Portugais (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 0,
|
||||
"id": "2.2937312;48.8711878",
|
||||
"name": "Avenue des Portugais",
|
||||
"coord": {
|
||||
"lat": "48.8711878",
|
||||
"lon": "2.2937312"
|
||||
},
|
||||
"label": "Avenue des Portugais (Paris)"
|
||||
},
|
||||
"id": "poi:n489537569",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "26",
|
||||
"name": "Avenue des Portugais",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "16001",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
50
types/testdata/container/correct/b5.json
vendored
Normal file
50
types/testdata/container/correct/b5.json
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n1379439290",
|
||||
"name": "Avenue des Ternes (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Avenue des Ternes",
|
||||
"coord": {
|
||||
"lat": "48.8794462",
|
||||
"lon": "2.2915207"
|
||||
},
|
||||
"label": "Avenue des Ternes (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 2,
|
||||
"id": "2.2915207;48.8794462",
|
||||
"name": "Place Tristan Bernard",
|
||||
"coord": {
|
||||
"lat": "48.8794462",
|
||||
"lon": "2.2915207"
|
||||
},
|
||||
"label": "2 Place Tristan Bernard (Paris)"
|
||||
},
|
||||
"id": "poi:n1379439290",
|
||||
"properties": {
|
||||
"operator": "JCDecaux",
|
||||
"amenity": "bicycle_rental",
|
||||
"ref": "17036",
|
||||
"name": "Avenue des Ternes",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b6.json
vendored
Normal file
51
types/testdata/container/correct/b6.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n272853313",
|
||||
"name": "Avenue Henri Martin (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Avenue Henri Martin",
|
||||
"coord": {
|
||||
"lat": "48.864084",
|
||||
"lon": "2.2768625"
|
||||
},
|
||||
"label": "Avenue Henri Martin (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 71,
|
||||
"id": "2.2768625;48.864084",
|
||||
"name": "Avenue Henri Martin",
|
||||
"coord": {
|
||||
"lat": "48.864084",
|
||||
"lon": "2.2768625"
|
||||
},
|
||||
"label": "71 Avenue Henri Martin (Paris)"
|
||||
},
|
||||
"id": "poi:n272853313",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "34",
|
||||
"name": "Avenue Henri Martin",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "16013",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b7.json
vendored
Normal file
51
types/testdata/container/correct/b7.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n1406238646",
|
||||
"name": "Avenue Rene Coty (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Avenue Rene Coty",
|
||||
"coord": {
|
||||
"lat": "48.8248017",
|
||||
"lon": "2.3362648"
|
||||
},
|
||||
"label": "Avenue Rene Coty (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 48,
|
||||
"id": "2.3362648;48.8248017",
|
||||
"name": "Avenue Reille",
|
||||
"coord": {
|
||||
"lat": "48.8248017",
|
||||
"lon": "2.3362648"
|
||||
},
|
||||
"label": "48 Avenue Reille (Paris)"
|
||||
},
|
||||
"id": "poi:n1406238646",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"name": "Avenue Rene Coty",
|
||||
"wheelchair": "no",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "14016",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/b8.json
vendored
Normal file
51
types/testdata/container/correct/b8.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 60,
|
||||
"id": "poi:n2304319864",
|
||||
"name": "10 Avenue des Minimes (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Parking v\u00e9lo",
|
||||
"id": "poi_type:amenity:bicycle_parking"
|
||||
},
|
||||
"name": "10 Avenue des Minimes",
|
||||
"coord": {
|
||||
"lat": "48.8411291",
|
||||
"lon": "2.4320878"
|
||||
},
|
||||
"label": "10 Avenue des Minimes (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 0,
|
||||
"id": "2.4320878;48.8411291",
|
||||
"name": "Rue Louis Besquel",
|
||||
"coord": {
|
||||
"lat": "48.8411291",
|
||||
"lon": "2.4320878"
|
||||
},
|
||||
"label": "Rue Louis Besquel"
|
||||
},
|
||||
"id": "poi:n2304319864",
|
||||
"properties": {
|
||||
"amenity": "bicycle_parking",
|
||||
"capacity": "20",
|
||||
"name": "10 Avenue des Minimes",
|
||||
"supervised": "no",
|
||||
"covered": "no",
|
||||
"bicycle_parking": "stands"
|
||||
}
|
||||
}
|
||||
}
|
||||
48
types/testdata/container/correct/b9.json
vendored
Normal file
48
types/testdata/container/correct/b9.json
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 60,
|
||||
"id": "poi:n4689211908",
|
||||
"name": "32 Avenue Anatole France (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Parking v\u00e9lo",
|
||||
"id": "poi_type:amenity:bicycle_parking"
|
||||
},
|
||||
"name": "32 Avenue Anatole France",
|
||||
"coord": {
|
||||
"lat": "48.8420506",
|
||||
"lon": "2.4316558"
|
||||
},
|
||||
"label": "32 Avenue Anatole France (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 47,
|
||||
"id": "2.4316558;48.8420506",
|
||||
"name": "Rue des Vignerons",
|
||||
"coord": {
|
||||
"lat": "48.8420506",
|
||||
"lon": "2.4316558"
|
||||
},
|
||||
"label": "47 Rue des Vignerons"
|
||||
},
|
||||
"id": "poi:n4689211908",
|
||||
"properties": {
|
||||
"source": "Mairie de Vincennes",
|
||||
"amenity": "bicycle_parking",
|
||||
"name": "32 Avenue Anatole France"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
types/testdata/container/correct/c0.json
vendored
Normal file
18
types/testdata/container/correct/c0.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"embedded_type": "administrative_region",
|
||||
"quality": 70,
|
||||
"administrative_region": {
|
||||
"insee": "7511559",
|
||||
"name": "Quartier de Grenelle",
|
||||
"level": 10,
|
||||
"coord": {
|
||||
"lat": "48.850168",
|
||||
"lon": "2.29184"
|
||||
},
|
||||
"label": "Quartier de Grenelle (75015)",
|
||||
"id": "admin:fr:7511559",
|
||||
"zip_code": "75015"
|
||||
},
|
||||
"id": "admin:fr:7511559",
|
||||
"name": "Quartier de Grenelle (75015)"
|
||||
}
|
||||
41
types/testdata/container/correct/c1.json
vendored
Normal file
41
types/testdata/container/correct/c1.json
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"embedded_type": "stop_area",
|
||||
"quality": 50,
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "RATLMPGR"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "LMPGR"
|
||||
}
|
||||
],
|
||||
"name": "La Motte-Picquet \u2014 Grenelle",
|
||||
"links": [],
|
||||
"coord": {
|
||||
"lat": "48.84916",
|
||||
"lon": "2.297949"
|
||||
},
|
||||
"label": "La Motte-Picquet \u2014 Grenelle (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"timezone": "Europe\/Paris",
|
||||
"id": "stop_area:RAT:SA:LMPGR"
|
||||
},
|
||||
"name": "La Motte-Picquet \u2014 Grenelle (Paris)",
|
||||
"id": "stop_area:RAT:SA:LMPGR"
|
||||
}
|
||||
52
types/testdata/container/correct/c2.json
vendored
Normal file
52
types/testdata/container/correct/c2.json
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:n305103675",
|
||||
"name": "Grenelle Violet (prop3) (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Grenelle Violet (prop3)",
|
||||
"coord": {
|
||||
"lat": "48.8499385",
|
||||
"lon": "2.2945842"
|
||||
},
|
||||
"label": "Grenelle Violet (prop3) (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 88,
|
||||
"id": "2.2945842;48.8499385",
|
||||
"name": "Boulevard de Grenelle",
|
||||
"coord": {
|
||||
"lat": "48.8499385",
|
||||
"lon": "2.2945842"
|
||||
},
|
||||
"label": "88 Boulevard de Grenelle (Paris)"
|
||||
},
|
||||
"id": "poi:n305103675",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "53",
|
||||
"name": "Grenelle Violet (prop3)",
|
||||
"wheelchair": "no",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "15106",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
50
types/testdata/container/correct/c3.json
vendored
Normal file
50
types/testdata/container/correct/c3.json
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 70,
|
||||
"id": "poi:w99577109",
|
||||
"name": "Square des Gr\u00e8s (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Parc, espace vert",
|
||||
"id": "poi_type:leisure:park"
|
||||
},
|
||||
"name": "Square des Gr\u00e8s",
|
||||
"coord": {
|
||||
"lat": "48.85956934",
|
||||
"lon": "2.406174677"
|
||||
},
|
||||
"label": "Square des Gr\u00e8s (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 14,
|
||||
"id": "2.406174677;48.85956934",
|
||||
"name": "Rue Riblette",
|
||||
"coord": {
|
||||
"lat": "48.85956934",
|
||||
"lon": "2.406174677"
|
||||
},
|
||||
"label": "14 Rue Riblette (Paris)"
|
||||
},
|
||||
"id": "poi:w99577109",
|
||||
"properties": {
|
||||
"wikipedia": "fr:Square des Gr\u00e8s",
|
||||
"wikidata": "Q3494997",
|
||||
"name": "Square des Gr\u00e8s",
|
||||
"leisure": "park",
|
||||
"start_date": "1983"
|
||||
}
|
||||
}
|
||||
}
|
||||
52
types/testdata/container/correct/c4.json
vendored
Normal file
52
types/testdata/container/correct/c4.json
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 50,
|
||||
"id": "poi:n672727214",
|
||||
"name": "Commissariat de police Javel-Grenelle (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Police, gendarmerie",
|
||||
"id": "poi_type:amenity:police"
|
||||
},
|
||||
"name": "Commissariat de police Javel-Grenelle",
|
||||
"coord": {
|
||||
"lat": "48.8433337",
|
||||
"lon": "2.2768334"
|
||||
},
|
||||
"label": "Commissariat de police Javel-Grenelle (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 34,
|
||||
"id": "2.2768334;48.8433337",
|
||||
"name": "Rue Balard",
|
||||
"coord": {
|
||||
"lat": "48.8433337",
|
||||
"lon": "2.2768334"
|
||||
},
|
||||
"label": "34 Commissariat de police Javel-Grenelle (Paris)"
|
||||
},
|
||||
"id": "poi:n672727214",
|
||||
"properties": {
|
||||
"addr:housenumber": "34",
|
||||
"amenity": "police",
|
||||
"addr:city": "Paris",
|
||||
"addr:postcode": "75015",
|
||||
"name": "Commissariat de police Javel-Grenelle",
|
||||
"addr:country": "FR",
|
||||
"addr:street": "Rue Balard"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/c5.json
vendored
Normal file
51
types/testdata/container/correct/c5.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 50,
|
||||
"id": "poi:n60751375",
|
||||
"name": "Sebastopol Grenata - 12 Rue Grenata - 75002 Paris (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Sebastopol Grenata - 12 Rue Grenata - 75002 Paris",
|
||||
"coord": {
|
||||
"lat": "48.8652569",
|
||||
"lon": "2.3516674"
|
||||
},
|
||||
"label": "Sebastopol Grenata - 12 Rue Grenata - 75002 Paris (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 12,
|
||||
"id": "2.3516674;48.8652569",
|
||||
"name": "Rue Greneta",
|
||||
"coord": {
|
||||
"lat": "48.8652569",
|
||||
"lon": "2.3516674"
|
||||
},
|
||||
"label": "12 Rue Greneta (Paris)"
|
||||
},
|
||||
"id": "poi:n60751375",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "34",
|
||||
"name": "Sebastopol Grenata - 12 Rue Grenata - 75002 Paris",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "2001",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/c6.json
vendored
Normal file
51
types/testdata/container/correct/c6.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 40,
|
||||
"id": "poi:n439924298",
|
||||
"name": "Grenier Saint-Lazare - 34 Rue Grenier Saint-Lazare - 75003 Paris (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Grenier Saint-Lazare - 34 Rue Grenier Saint-Lazare - 75003 Paris",
|
||||
"coord": {
|
||||
"lat": "48.8631013",
|
||||
"lon": "2.3527609"
|
||||
},
|
||||
"label": "Grenier Saint-Lazare - 34 Rue Grenier Saint-Lazare - 75003 Paris (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 34,
|
||||
"id": "2.3527609;48.8631013",
|
||||
"name": "Rue du Grenier Saint-Lazare",
|
||||
"coord": {
|
||||
"lat": "48.8631013",
|
||||
"lon": "2.3527609"
|
||||
},
|
||||
"label": "34 Rue du Grenier Saint-Lazare (Paris)"
|
||||
},
|
||||
"id": "poi:n439924298",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "31",
|
||||
"name": "Grenier Saint-Lazare - 34 Rue Grenier Saint-Lazare - 75003 Paris",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "3014",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
types/testdata/container/correct/c7.json
vendored
Normal file
51
types/testdata/container/correct/c7.json
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"embedded_type": "poi",
|
||||
"quality": 40,
|
||||
"id": "poi:n310350237",
|
||||
"name": "Square Bela Bartok - Quai Grenelle - 75015 Paris (Paris)",
|
||||
"poi": {
|
||||
"poi_type": {
|
||||
"name": "Station VLS",
|
||||
"id": "poi_type:amenity:bicycle_rental"
|
||||
},
|
||||
"name": "Square Bela Bartok - Quai Grenelle - 75015 Paris",
|
||||
"coord": {
|
||||
"lat": "48.8511757",
|
||||
"lon": "2.2845792"
|
||||
},
|
||||
"label": "Square Bela Bartok - Quai Grenelle - 75015 Paris (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"house_number": 2,
|
||||
"id": "2.2845792;48.8511757",
|
||||
"name": "Place de Brazzaville",
|
||||
"coord": {
|
||||
"lat": "48.8511757",
|
||||
"lon": "2.2845792"
|
||||
},
|
||||
"label": "2 Place de Brazzaville (Paris)"
|
||||
},
|
||||
"id": "poi:n310350237",
|
||||
"properties": {
|
||||
"amenity": "bicycle_rental",
|
||||
"capacity": "23",
|
||||
"name": "Square Bela Bartok - Quai Grenelle - 75015 Paris",
|
||||
"operator": "JCDecaux",
|
||||
"ref": "15102",
|
||||
"network": "V\u00e9lib'"
|
||||
}
|
||||
}
|
||||
}
|
||||
30
types/testdata/container/correct/c8.json
vendored
Normal file
30
types/testdata/container/correct/c8.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"embedded_type": "address",
|
||||
"quality": 80,
|
||||
"id": "2.397900446545659;48.86088080214612",
|
||||
"name": "Avenue Greffulhe (Paris)",
|
||||
"address": {
|
||||
"name": "Avenue Greffulhe",
|
||||
"house_number": 0,
|
||||
"coord": {
|
||||
"lat": "48.8608808021",
|
||||
"lon": "2.39790044655"
|
||||
},
|
||||
"label": "Avenue Greffulhe (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"id": "2.397900446545659;48.86088080214612"
|
||||
}
|
||||
}
|
||||
30
types/testdata/container/correct/c9.json
vendored
Normal file
30
types/testdata/container/correct/c9.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"embedded_type": "address",
|
||||
"quality": 80,
|
||||
"id": "2.35;48.8652835",
|
||||
"name": "Cour Greneta (Paris)",
|
||||
"address": {
|
||||
"name": "Cour Greneta",
|
||||
"house_number": 0,
|
||||
"coord": {
|
||||
"lat": "48.8652835",
|
||||
"lon": "2.35"
|
||||
},
|
||||
"label": "Cour Greneta (Paris)",
|
||||
"administrative_regions": [
|
||||
{
|
||||
"insee": "75056",
|
||||
"name": "Paris",
|
||||
"level": 8,
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"label": "Paris",
|
||||
"id": "admin:fr:75056",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"id": "2.35;48.8652835"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
panic: runtime error: invalid memory address or nil pointer dereference
|
||||
[signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x4f1cdb]
|
||||
|
||||
goroutine 1 [running]:
|
||||
github.com/aabizri/gonavitia/types.FuzzPlaceCountainer(0x7fa709ccf000, 0x2, 0x200000, 0x3)
|
||||
/tmp/go-fuzz-build662575377/gopath/src/github.com/aabizri/gonavitia/types/place_fuzz.go:22 +0x18b
|
||||
go-fuzz-dep.Main(0x53b308)
|
||||
/tmp/go-fuzz-build662575377/goroot/src/go-fuzz-dep/main.go:49 +0xde
|
||||
main.main()
|
||||
/tmp/go-fuzz-build662575377/gopath/src/github.com/aabizri/gonavitia/types/go.fuzz.main/main.go:10 +0x2d
|
||||
exit status 2
|
||||
|
|
@ -0,0 +1 @@
|
|||
"{}"
|
||||
202
types/testdata/create.go
vendored
Normal file
202
types/testdata/create.go
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type journeyRequest struct {
|
||||
Journeys []json.RawMessage `json:"journeys"`
|
||||
}
|
||||
|
||||
func (r journeyRequest) messages() []json.RawMessage {
|
||||
return r.Journeys
|
||||
}
|
||||
|
||||
type placeRequest struct {
|
||||
Places []json.RawMessage `json:"places"`
|
||||
}
|
||||
|
||||
func (r placeRequest) messages() []json.RawMessage {
|
||||
return r.Places
|
||||
}
|
||||
|
||||
type coverageRequest struct {
|
||||
Regions []json.RawMessage `json:"regions"`
|
||||
}
|
||||
|
||||
func (r coverageRequest) messages() []json.RawMessage {
|
||||
return r.Regions
|
||||
}
|
||||
|
||||
type request interface {
|
||||
messages() []json.RawMessage
|
||||
}
|
||||
|
||||
var (
|
||||
originFlag = flag.String("from", "../../testdata", "Original directory")
|
||||
destinationFlag = flag.String("to", "./", "Destination directory")
|
||||
originPath string
|
||||
destinationPath string
|
||||
)
|
||||
|
||||
var equivalence = map[string]string{
|
||||
"journeys": "journey",
|
||||
"places": "place",
|
||||
"coverage": "region",
|
||||
}
|
||||
|
||||
// load loads the origin directory and files
|
||||
func load(path string) (map[string][]*os.File, error) {
|
||||
subDirs, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while listing subdirs !: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
originFiles := make(map[string][]*os.File, len(subDirs))
|
||||
|
||||
for _, dinfo := range subDirs {
|
||||
dirName := dinfo.Name()
|
||||
if !dinfo.IsDir() {
|
||||
fmt.Printf("Skipping %s...\n", dirName)
|
||||
} else {
|
||||
fmt.Printf("Processing %s directory...\n", dirName)
|
||||
files, err := ioutil.ReadDir(filepath.Join(originPath, dirName))
|
||||
if err != nil {
|
||||
fmt.Printf("Error while reading %s directory !: %v\n", dirName, err)
|
||||
return originFiles, err
|
||||
}
|
||||
for _, finfo := range files {
|
||||
fmt.Printf("\tProcessing %s...\n", finfo.Name())
|
||||
fname := finfo.Name()
|
||||
if fname[len(fname)-4:] != "json" {
|
||||
fmt.Printf("\t\tSkipping\n")
|
||||
} else {
|
||||
path := filepath.Join(originPath, dirName, fname)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Printf("\t\tError while opening file %s! : %v\n", path, err)
|
||||
}
|
||||
originFiles[dirName] = append(originFiles[dirName], f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originFiles, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if filepath.IsAbs(*originFlag) {
|
||||
originPath = *originFlag
|
||||
} else {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("Error while retrieving working directory, please retry with an absolute path: %v\n", err)
|
||||
return
|
||||
}
|
||||
originPath = filepath.Join(wd, *originFlag)
|
||||
}
|
||||
|
||||
if filepath.IsAbs(*destinationFlag) {
|
||||
destinationPath = *destinationFlag
|
||||
} else {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("Error while retrieving working directory, please retry with an absolute path: %v\n", err)
|
||||
return
|
||||
}
|
||||
destinationPath = filepath.Join(wd, *destinationFlag)
|
||||
}
|
||||
|
||||
originFiles, err := load(originPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v", err)
|
||||
}
|
||||
|
||||
// For each of them, process them
|
||||
for cat, files := range originFiles {
|
||||
cat = equivalence[cat]
|
||||
fmt.Printf("Printing %s...\n", cat)
|
||||
for _, file := range files {
|
||||
fmt.Printf("Dealing with one origin-file...\n")
|
||||
// Prepare decoder
|
||||
dec := json.NewDecoder(file)
|
||||
|
||||
// Create hosting structure & decode to it
|
||||
var req request
|
||||
switch cat {
|
||||
case "journey":
|
||||
tmp := &journeyRequest{}
|
||||
// Decode to it
|
||||
err := dec.Decode(tmp)
|
||||
if err != nil {
|
||||
stat, _ := file.Stat()
|
||||
fmt.Printf("Error while decoding %s: %v\n", stat.Name(), err)
|
||||
return
|
||||
}
|
||||
req = tmp
|
||||
case "place":
|
||||
tmp := &placeRequest{}
|
||||
// Decode to it
|
||||
err := dec.Decode(tmp)
|
||||
if err != nil {
|
||||
stat, _ := file.Stat()
|
||||
fmt.Printf("Error while decoding %s: %v\n", stat.Name(), err)
|
||||
return
|
||||
}
|
||||
req = tmp
|
||||
case "region":
|
||||
tmp := &coverageRequest{}
|
||||
// Decode to it
|
||||
err := dec.Decode(tmp)
|
||||
if err != nil {
|
||||
stat, _ := file.Stat()
|
||||
fmt.Printf("Error while decoding %s: %v\n", stat.Name(), err)
|
||||
return
|
||||
}
|
||||
req = tmp
|
||||
default:
|
||||
fmt.Printf("Incorrect category")
|
||||
return
|
||||
}
|
||||
|
||||
// Get file stat
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
fmt.Printf("Error while retrieving file stat: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Now for each Journey, create a new file and write to it
|
||||
for i, message := range req.messages() {
|
||||
// Create the file name
|
||||
nname := fmt.Sprintf("%s%d.json", stat.Name()[:len(stat.Name())-5], i)
|
||||
npath := filepath.Join(destinationPath, cat, nname)
|
||||
|
||||
// Create the file
|
||||
nfile, err := os.Create(npath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while creating file %s: %v\n", npath, err)
|
||||
}
|
||||
|
||||
// Write to it
|
||||
enc := json.NewEncoder(nfile)
|
||||
enc.SetIndent("", "\t")
|
||||
err = enc.Encode(message)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while writing to file %s: %v\n", npath, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
types/testdata/disruption/correct/doc.json
vendored
Normal file
23
types/testdata/disruption/correct/doc.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"id": "ce7e265d-5762-45b6-ab4d-a1df643dd48d",
|
||||
"status": "active",
|
||||
"disruption_id": "ce7e265d-5762-45b6-ab4d-a1df643dd48d",
|
||||
"impact_id": "ce7e265d-5762-45b6-ab4d-a1df643dd48d",
|
||||
"severity": {
|
||||
"name": "trip delayed",
|
||||
"effect": "SIGNIFICANT_DELAYS"
|
||||
},
|
||||
"application_periods": [
|
||||
{
|
||||
"begin": "20160608T215400",
|
||||
"end": "20160608T230959"
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{"text": "Strike"}
|
||||
],
|
||||
"updated_at": "20160617T132624",
|
||||
"impacted_objects": [],
|
||||
"cause": "Cause...",
|
||||
"category": "incident"
|
||||
}
|
||||
1821
types/testdata/journey/bench/heavy-nogeojson.json
vendored
Normal file
1821
types/testdata/journey/bench/heavy-nogeojson.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
2135
types/testdata/journey/bench/heavy.json
vendored
Normal file
2135
types/testdata/journey/bench/heavy.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
898
types/testdata/journey/bench/light-nogeojson.json
vendored
Normal file
898
types/testdata/journey/bench/light-nogeojson.json
vendored
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
{
|
||||
"arrival_date_time": "20170413T141146",
|
||||
"calendars": [
|
||||
{
|
||||
"active_periods": [
|
||||
{
|
||||
"begin": "20170326",
|
||||
"end": "20170417"
|
||||
}
|
||||
],
|
||||
"exceptions": [
|
||||
{
|
||||
"datetime": "20170402",
|
||||
"type": "remove"
|
||||
},
|
||||
{
|
||||
"datetime": "20170409",
|
||||
"type": "remove"
|
||||
},
|
||||
{
|
||||
"datetime": "20170415",
|
||||
"type": "add"
|
||||
}
|
||||
],
|
||||
"week_pattern": {
|
||||
"friday": true,
|
||||
"monday": true,
|
||||
"saturday": false,
|
||||
"sunday": true,
|
||||
"thursday": true,
|
||||
"tuesday": true,
|
||||
"wednesday": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"co2_emission": {
|
||||
"unit": "gEC",
|
||||
"value": 39.556
|
||||
},
|
||||
"departure_date_time": "20170413T133945",
|
||||
"duration": 1921,
|
||||
"durations": {
|
||||
"total": 1921,
|
||||
"walking": 1081
|
||||
},
|
||||
"fare": {
|
||||
"found": false,
|
||||
"links": [],
|
||||
"total": {
|
||||
"currency": "",
|
||||
"value": "0.0"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.navitia.io/v1/coverage/fr-idf/journeys?allowed_id%5B%5D=stop_area%3AOIF%3ASA%3A8739305\u0026allowed_id%5B%5D=stop_area%3AOIF%3ASA%3A8754700\u0026to=2.2922926%3B48.8583736\u0026from=2.3749036%3B48.8467927\u0026min_nb_journeys=5",
|
||||
"rel": "same_journey_schedules",
|
||||
"templated": false,
|
||||
"type": "journeys"
|
||||
}
|
||||
],
|
||||
"nb_transfers": 0,
|
||||
"requested_date_time": "20170413T133734",
|
||||
"sections": [
|
||||
{
|
||||
"arrival_date_time": "20170413T135400",
|
||||
"co2_emission": {
|
||||
"unit": "",
|
||||
"value": 0.0
|
||||
},
|
||||
"departure_date_time": "20170413T133945",
|
||||
"duration": 855,
|
||||
"from": {
|
||||
"address": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
},
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8467927",
|
||||
"lon": "2.3749036"
|
||||
},
|
||||
"house_number": 9,
|
||||
"id": "2.3749036;48.8467927",
|
||||
"label": "9 Rue Abel (Paris)",
|
||||
"name": "Rue Abel"
|
||||
},
|
||||
"embedded_type": "address",
|
||||
"id": "2.3749036;48.8467927",
|
||||
"name": "9 Rue Abel (Paris)",
|
||||
"quality": 0
|
||||
},
|
||||
"id": "section_12_0",
|
||||
"links": [],
|
||||
"mode": "walking",
|
||||
"path": [
|
||||
{
|
||||
"direction": 0,
|
||||
"duration": 90,
|
||||
"length": 101,
|
||||
"name": "Rue Abel"
|
||||
},
|
||||
{
|
||||
"direction": 22,
|
||||
"duration": 14,
|
||||
"length": 16,
|
||||
"name": "Boulevard Diderot"
|
||||
},
|
||||
{
|
||||
"direction": -7,
|
||||
"duration": 328,
|
||||
"length": 367,
|
||||
"name": "Rue Van Gogh"
|
||||
},
|
||||
{
|
||||
"direction": 8,
|
||||
"duration": 40,
|
||||
"length": 45,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -4,
|
||||
"duration": 183,
|
||||
"length": 205,
|
||||
"name": "Pont Charles de Gaulle"
|
||||
},
|
||||
{
|
||||
"direction": -6,
|
||||
"duration": 13,
|
||||
"length": 15,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 90,
|
||||
"duration": 161,
|
||||
"length": 180,
|
||||
"name": "Quai d'Austerlitz"
|
||||
},
|
||||
{
|
||||
"direction": -87,
|
||||
"duration": 26,
|
||||
"length": 29,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 0,
|
||||
"duration": 0,
|
||||
"length": 0,
|
||||
"name": "Cour Seine"
|
||||
}
|
||||
],
|
||||
"to": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"name": "Gare d'Austerlitz RER C (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41333"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754702:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754702:800:C"
|
||||
}
|
||||
],
|
||||
"commercial_modes": [
|
||||
{
|
||||
"id": "commercial_mode:rapidtransit",
|
||||
"name": "RER"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.842528",
|
||||
"lon": "2.365433"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"label": "Gare d'Austerlitz RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz RER C",
|
||||
"physical_modes": [
|
||||
{
|
||||
"id": "physical_mode:RapidTransit",
|
||||
"name": "Train de banlieue / RER"
|
||||
}
|
||||
],
|
||||
"stop_area": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754700"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8754700"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.843578",
|
||||
"lon": "2.364651"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8754700",
|
||||
"label": "Gare d'Austerlitz (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "street_network"
|
||||
},
|
||||
{
|
||||
"additional_informations": [
|
||||
"regular"
|
||||
],
|
||||
"arrival_date_time": "20170413T140800",
|
||||
"base_arrival_date_time": "20170413T140800",
|
||||
"base_departure_date_time": "20170413T135400",
|
||||
"co2_emission": {
|
||||
"unit": "gEC",
|
||||
"value": 39.556
|
||||
},
|
||||
"departure_date_time": "20170413T135400",
|
||||
"display_informations": {
|
||||
"code": "C",
|
||||
"color": "FCD946",
|
||||
"commercial_mode": "RER",
|
||||
"description": "",
|
||||
"direction": "Gare de Versailles Ch\u00e2teau - Rive Gauche (Versailles)",
|
||||
"equipments": [],
|
||||
"headsign": "VICK",
|
||||
"label": "C",
|
||||
"links": [],
|
||||
"network": "RER",
|
||||
"physical_mode": "Train de banlieue / RER",
|
||||
"text_color": "FFFFFF"
|
||||
},
|
||||
"duration": 840,
|
||||
"from": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"name": "Gare d'Austerlitz RER C (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41333"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754702:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754702:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.842528",
|
||||
"lon": "2.365433"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"label": "Gare d'Austerlitz RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz RER C",
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754700"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8754700"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.843578",
|
||||
"lon": "2.364651"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8754700",
|
||||
"label": "Gare d'Austerlitz (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "section_13_0",
|
||||
"links": [
|
||||
{
|
||||
"id": "vehicle_journey:OIF:82209457-1_354848-1_dst_2",
|
||||
"type": "vehicle_journey"
|
||||
},
|
||||
{
|
||||
"id": "line:OIF:800:COIF741",
|
||||
"type": "line"
|
||||
},
|
||||
{
|
||||
"id": "route:OIF:800:C_R",
|
||||
"type": "route"
|
||||
},
|
||||
{
|
||||
"id": "commercial_mode:rapidtransit",
|
||||
"type": "commercial_mode"
|
||||
},
|
||||
{
|
||||
"id": "physical_mode:RapidTransit",
|
||||
"type": "physical_mode"
|
||||
},
|
||||
{
|
||||
"id": "network:RER",
|
||||
"type": "network"
|
||||
}
|
||||
],
|
||||
"stop_date_times": [
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T135300",
|
||||
"base_arrival_date_time": "20170413T135300",
|
||||
"base_departure_date_time": "20170413T135400",
|
||||
"departure_date_time": "20170413T135400",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41333"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754702:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754702:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.842528",
|
||||
"lon": "2.365433"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"label": "Gare d'Austerlitz RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz RER C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T135700",
|
||||
"base_arrival_date_time": "20170413T135700",
|
||||
"base_departure_date_time": "20170413T135800",
|
||||
"departure_date_time": "20170413T135800",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41335"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754731:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754731:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.853336",
|
||||
"lon": "2.346035"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754731:800:C",
|
||||
"label": "Saint-Michel Notre-Dame RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Saint-Michel Notre-Dame RER C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140000",
|
||||
"base_arrival_date_time": "20170413T140000",
|
||||
"base_departure_date_time": "20170413T140100",
|
||||
"departure_date_time": "20170413T140100",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41334"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754730:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754730:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.860708",
|
||||
"lon": "2.32562"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754730:800:C",
|
||||
"label": "Mus\u00e9e d'Orsay (Paris)",
|
||||
"links": [],
|
||||
"name": "Mus\u00e9e d'Orsay"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140300",
|
||||
"base_arrival_date_time": "20170413T140300",
|
||||
"base_departure_date_time": "20170413T140400",
|
||||
"departure_date_time": "20170413T140400",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41208"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739303:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739303:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.862902",
|
||||
"lon": "2.313911"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739303:800:C",
|
||||
"label": "Invalides (Paris)",
|
||||
"links": [],
|
||||
"name": "Invalides"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140600",
|
||||
"base_arrival_date_time": "20170413T140600",
|
||||
"base_departure_date_time": "20170413T140600",
|
||||
"departure_date_time": "20170413T140600",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41209"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739304:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739304:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.862662",
|
||||
"lon": "2.30099"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739304:800:C",
|
||||
"label": "Pont de l'Alma (Paris)",
|
||||
"links": [],
|
||||
"name": "Pont de l'Alma"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140800",
|
||||
"base_arrival_date_time": "20170413T140800",
|
||||
"base_departure_date_time": "20170413T140900",
|
||||
"departure_date_time": "20170413T140900",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.857293",
|
||||
"lon": "2.290392"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"to": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"name": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.857293",
|
||||
"lon": "2.290392"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8739305"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8572",
|
||||
"lon": "2.293234"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8739305",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "public_transport"
|
||||
},
|
||||
{
|
||||
"arrival_date_time": "20170413T141146",
|
||||
"co2_emission": {
|
||||
"unit": "",
|
||||
"value": 0.0
|
||||
},
|
||||
"departure_date_time": "20170413T140800",
|
||||
"duration": 226,
|
||||
"from": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"name": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
},
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
}
|
||||
],
|
||||
"commercial_modes": [
|
||||
{
|
||||
"id": "commercial_mode:rapidtransit",
|
||||
"name": "RER"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.857293",
|
||||
"lon": "2.290392"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"physical_modes": [
|
||||
{
|
||||
"id": "physical_mode:RapidTransit",
|
||||
"name": "Train de banlieue / RER"
|
||||
}
|
||||
],
|
||||
"stop_area": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8739305"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8739305"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8572",
|
||||
"lon": "2.293234"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8739305",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "section_14_0",
|
||||
"links": [],
|
||||
"mode": "walking",
|
||||
"path": [
|
||||
{
|
||||
"direction": 0,
|
||||
"duration": 19,
|
||||
"length": 21,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 0,
|
||||
"duration": 14,
|
||||
"length": 16,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 3,
|
||||
"duration": 5,
|
||||
"length": 6,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 13,
|
||||
"duration": 28,
|
||||
"length": 31,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -101,
|
||||
"duration": 3,
|
||||
"length": 3,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -4,
|
||||
"duration": 5,
|
||||
"length": 6,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 3,
|
||||
"duration": 3,
|
||||
"length": 3,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -89,
|
||||
"duration": 17,
|
||||
"length": 19,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 93,
|
||||
"duration": 132,
|
||||
"length": 148,
|
||||
"name": "Quai Branly"
|
||||
}
|
||||
],
|
||||
"to": {
|
||||
"address": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
},
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8583736",
|
||||
"lon": "2.2922926"
|
||||
},
|
||||
"house_number": 69,
|
||||
"id": "2.2922926;48.8583736",
|
||||
"label": "69 Quai Branly (Paris)",
|
||||
"name": "Quai Branly"
|
||||
},
|
||||
"embedded_type": "address",
|
||||
"id": "2.2922926;48.8583736",
|
||||
"name": "69 Quai Branly (Paris)",
|
||||
"quality": 0
|
||||
},
|
||||
"type": "street_network"
|
||||
}
|
||||
],
|
||||
"status": "",
|
||||
"tags": [
|
||||
"walking",
|
||||
"ecologic"
|
||||
],
|
||||
"type": "best"
|
||||
}
|
||||
1112
types/testdata/journey/bench/light.json
vendored
Normal file
1112
types/testdata/journey/bench/light.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1565
types/testdata/journey/bench/regular-nogeojson.json
vendored
Normal file
1565
types/testdata/journey/bench/regular-nogeojson.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1863
types/testdata/journey/bench/regular.json
vendored
Normal file
1863
types/testdata/journey/bench/regular.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
2135
types/testdata/journey/correct/a0.json
vendored
Normal file
2135
types/testdata/journey/correct/a0.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1863
types/testdata/journey/correct/a1.json
vendored
Normal file
1863
types/testdata/journey/correct/a1.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1564
types/testdata/journey/correct/a2.json
vendored
Normal file
1564
types/testdata/journey/correct/a2.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1112
types/testdata/journey/correct/b0.json
vendored
Normal file
1112
types/testdata/journey/correct/b0.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
1999
types/testdata/journey/correct/b1.json
vendored
Normal file
1999
types/testdata/journey/correct/b1.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
799
types/testdata/journey/crashers/edf90467f065ecd656b1a49052393ee870bbdb04
vendored
Normal file
799
types/testdata/journey/crashers/edf90467f065ecd656b1a49052393ee870bbdb04
vendored
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
{"":"","":[{"":[{"":"","":""}],"":[{"":"","":""},{"":"","":""},{"":"","":""}],"":{"":true,"":true,"":false,"":true,"":true,"":true,"":true}}],"":{"":"","":9},"":"","":1,"":{"":1,"":1},"":{"":false,"":[],"":{"":"","":""}},"":[{"":"","":"","":false,"":""}],"":0,"":"","sections":[{"":"","":{"":"","":0.0},"":"","":5,"":{"":{"":[{"":{"":"","":""},"":"","":"","":"","":8,"":"","":""},{"":{"":"","":""},"":"","":"","":"","":8,"":"","":""}],"":{"":"","":""},"":9,"":"","":"","":""},"":"","":"","":"","":0},"":{"":[[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8],[2,8]],"":[{"":7}],"":""},"":"","":[],"":"","":[{"":0,"":0,"":1,"":""},{"":2,"":4,"":6,"":""},{"":7,"":8,"":7,"":""},{"":8,"":0,"":5,"":""},{"":4,"":3,"":2,"":""},{"":6,"":3,"":5,"":""},{"":0,"":1,"":0,"":""},{"":7,"":6,"":9,"":""},{"":0,"":0,"":0,"":""}],"":{"":"","":"","":"","":0,"":{"":[{"":{"":"","":""},"":"","":"","":"","":8,"":"","":""}],"":[{"":"","":""},{"":"","":""},{"":"","":""}],"commercial_modes": [
|
||||
{
|
||||
"id": "commercial_mode:rapidtransit",
|
||||
"name": "RER"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.842528",
|
||||
"lon": "2.365433"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"label": "Gare d'Austerlitz RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz RER C",
|
||||
"physical_modes": [
|
||||
{
|
||||
"id": "physical_mode:RapidTransit",
|
||||
"name": "Train de banlieue / RER"
|
||||
}
|
||||
],
|
||||
"stop_area": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754700"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8754700"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.843578",
|
||||
"lon": "2.364651"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8754700",
|
||||
"label": "Gare d'Austerlitz (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "street_network"
|
||||
},
|
||||
{
|
||||
"additional_informations": [
|
||||
"regular"
|
||||
],
|
||||
"arrival_date_time": "20170413T140800",
|
||||
"base_arrival_date_time": "20170413T140800",
|
||||
"base_departure_date_time": "20170413T135400",
|
||||
"co2_emission": {
|
||||
"unit": "gEC",
|
||||
"value": 39.556
|
||||
},
|
||||
"departure_date_time": "20170413T135400",
|
||||
"display_informations": {
|
||||
"code": "C",
|
||||
"color": "FCD946",
|
||||
"commercial_mode": "RER",
|
||||
"description": "",
|
||||
"direction": "Gare de Versailles Ch\u00e2teau - Rive Gauche (Versailles)",
|
||||
"equipments": [],
|
||||
"headsign": "VICK",
|
||||
"label": "C",
|
||||
"links": [],
|
||||
"network": "RER",
|
||||
"physical_mode": "Train de banlieue / RER",
|
||||
"text_color": "FFFFFF"
|
||||
},
|
||||
"duration": 840,
|
||||
"from": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"name": "Gare d'Austerlitz RER C (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41333"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754702:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754702:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.842528",
|
||||
"lon": "2.365433"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"label": "Gare d'Austerlitz RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz RER C",
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754700"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8754700"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.843578",
|
||||
"lon": "2.364651"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8754700",
|
||||
"label": "Gare d'Austerlitz (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geojson": {
|
||||
"coordinate": [
|
||||
[
|
||||
2.365433,
|
||||
48.842528
|
||||
],
|
||||
[
|
||||
2.346035,
|
||||
48.853336
|
||||
],
|
||||
[
|
||||
2.32562,
|
||||
48.860708
|
||||
],
|
||||
[
|
||||
2.313911,
|
||||
48.862902
|
||||
],
|
||||
[
|
||||
2.30099,
|
||||
48.862662
|
||||
],
|
||||
[
|
||||
2.290392,
|
||||
48.857293
|
||||
]
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"length": 6380
|
||||
}
|
||||
],
|
||||
"type": "LineString"
|
||||
},
|
||||
"id": "section_13_0",
|
||||
"links": [
|
||||
{
|
||||
"id": "vehicle_journey:OIF:82209457-1_354848-1_dst_2",
|
||||
"type": "vehicle_journey"
|
||||
},
|
||||
{
|
||||
"id": "line:OIF:800:COIF741",
|
||||
"type": "line"
|
||||
},
|
||||
{
|
||||
"id": "route:OIF:800:C_R",
|
||||
"type": "route"
|
||||
},
|
||||
{
|
||||
"id": "commercial_mode:rapidtransit",
|
||||
"type": "commercial_mode"
|
||||
},
|
||||
{
|
||||
"id": "physical_mode:RapidTransit",
|
||||
"type": "physical_mode"
|
||||
},
|
||||
{
|
||||
"id": "network:RER",
|
||||
"type": "network"
|
||||
}
|
||||
],
|
||||
"stop_date_times": [
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T135300",
|
||||
"base_arrival_date_time": "20170413T135300",
|
||||
"base_departure_date_time": "20170413T135400",
|
||||
"departure_date_time": "20170413T135400",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41333"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754702:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754702:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.842528",
|
||||
"lon": "2.365433"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754702:800:C",
|
||||
"label": "Gare d'Austerlitz RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Gare d'Austerlitz RER C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T135700",
|
||||
"base_arrival_date_time": "20170413T135700",
|
||||
"base_departure_date_time": "20170413T135800",
|
||||
"departure_date_time": "20170413T135800",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41335"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754731:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754731:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.853336",
|
||||
"lon": "2.346035"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754731:800:C",
|
||||
"label": "Saint-Michel Notre-Dame RER C (Paris)",
|
||||
"links": [],
|
||||
"name": "Saint-Michel Notre-Dame RER C"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140000",
|
||||
"base_arrival_date_time": "20170413T140000",
|
||||
"base_departure_date_time": "20170413T140100",
|
||||
"departure_date_time": "20170413T140100",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41334"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8754730:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8754730:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.860708",
|
||||
"lon": "2.32562"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8754730:800:C",
|
||||
"label": "Mus\u00e9e d'Orsay (Paris)",
|
||||
"links": [],
|
||||
"name": "Mus\u00e9e d'Orsay"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140300",
|
||||
"base_arrival_date_time": "20170413T140300",
|
||||
"base_departure_date_time": "20170413T140400",
|
||||
"departure_date_time": "20170413T140400",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41208"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739303:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739303:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.862902",
|
||||
"lon": "2.313911"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739303:800:C",
|
||||
"label": "Invalides (Paris)",
|
||||
"links": [],
|
||||
"name": "Invalides"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140600",
|
||||
"base_arrival_date_time": "20170413T140600",
|
||||
"base_departure_date_time": "20170413T140600",
|
||||
"departure_date_time": "20170413T140600",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41209"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739304:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739304:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.862662",
|
||||
"lon": "2.30099"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739304:800:C",
|
||||
"label": "Pont de l'Alma (Paris)",
|
||||
"links": [],
|
||||
"name": "Pont de l'Alma"
|
||||
}
|
||||
},
|
||||
{
|
||||
"additional_informations": [],
|
||||
"arrival_date_time": "20170413T140800",
|
||||
"base_arrival_date_time": "20170413T140800",
|
||||
"base_departure_date_time": "20170413T140900",
|
||||
"departure_date_time": "20170413T140900",
|
||||
"links": [],
|
||||
"stop_point": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.857293",
|
||||
"lon": "2.290392"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"to": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"name": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.857293",
|
||||
"lon": "2.290392"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"stop_area": {
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8739305"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8572",
|
||||
"lon": "2.293234"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8739305",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "public_transport"
|
||||
},
|
||||
{
|
||||
"arrival_date_time": "20170413T141146",
|
||||
"co2_emission": {
|
||||
"unit": "",
|
||||
"value": 0.0
|
||||
},
|
||||
"departure_date_time": "20170413T140800",
|
||||
"duration": 226,
|
||||
"from": {
|
||||
"embedded_type": "stop_point",
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"name": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"quality": 0,
|
||||
"stop_point": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
},
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "ZDEr_ID_REF_A",
|
||||
"value": "41210"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305:800:C"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopPoint:8739305:800:C"
|
||||
}
|
||||
],
|
||||
"commercial_modes": [
|
||||
{
|
||||
"id": "commercial_mode:rapidtransit",
|
||||
"name": "RER"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.857293",
|
||||
"lon": "2.290392"
|
||||
},
|
||||
"equipments": [],
|
||||
"id": "stop_point:OIF:SP:8739305:800:C",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"physical_modes": [
|
||||
{
|
||||
"id": "physical_mode:RapidTransit",
|
||||
"name": "Train de banlieue / RER"
|
||||
}
|
||||
],
|
||||
"stop_area": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8739305"
|
||||
},
|
||||
{
|
||||
"type": "external_code",
|
||||
"value": "OIF8739305"
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"value": "StopArea:8739305"
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8572",
|
||||
"lon": "2.293234"
|
||||
},
|
||||
"id": "stop_area:OIF:SA:8739305",
|
||||
"label": "Champ de Mars Tour Eiffel (Paris)",
|
||||
"links": [],
|
||||
"name": "Champ de Mars Tour Eiffel",
|
||||
"timezone": "Europe/Paris"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geojson": {
|
||||
"coordinates": [
|
||||
[
|
||||
2.290392,
|
||||
48.857293
|
||||
],
|
||||
[
|
||||
2.2902998397,
|
||||
48.8574374498
|
||||
],
|
||||
[
|
||||
2.290517,
|
||||
48.857576
|
||||
],
|
||||
[
|
||||
2.290641,
|
||||
48.857504
|
||||
],
|
||||
[
|
||||
2.290688,
|
||||
48.857477
|
||||
],
|
||||
[
|
||||
2.290747,
|
||||
48.857439
|
||||
],
|
||||
[
|
||||
2.290973,
|
||||
48.857209
|
||||
],
|
||||
[
|
||||
2.291011,
|
||||
48.857233
|
||||
],
|
||||
[
|
||||
2.291064,
|
||||
48.857272
|
||||
],
|
||||
[
|
||||
2.291097,
|
||||
48.857294
|
||||
],
|
||||
[
|
||||
2.291075,
|
||||
48.857309
|
||||
],
|
||||
[
|
||||
2.290908,
|
||||
48.857414
|
||||
],
|
||||
[
|
||||
2.291025,
|
||||
48.857486
|
||||
],
|
||||
[
|
||||
2.291388,
|
||||
48.857826
|
||||
],
|
||||
[
|
||||
2.2922745574,
|
||||
48.8584013995
|
||||
],
|
||||
[
|
||||
2.2922745574,
|
||||
48.8584013995
|
||||
]
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"length": 253
|
||||
}
|
||||
],
|
||||
"type": "LineString"
|
||||
},
|
||||
"id": "section_14_0",
|
||||
"links": [],
|
||||
"mode": "walking",
|
||||
"path": [
|
||||
{
|
||||
"direction": 0,
|
||||
"duration": 19,
|
||||
"length": 21,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 0,
|
||||
"duration": 14,
|
||||
"length": 16,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 3,
|
||||
"duration": 5,
|
||||
"length": 6,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 13,
|
||||
"duration": 28,
|
||||
"length": 31,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -101,
|
||||
"duration": 3,
|
||||
"length": 3,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -4,
|
||||
"duration": 5,
|
||||
"length": 6,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 3,
|
||||
"duration": 3,
|
||||
"length": 3,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": -89,
|
||||
"duration": 17,
|
||||
"length": 19,
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"direction": 93,
|
||||
"duration": 132,
|
||||
"length": 148,
|
||||
"name": "Quai Branly"
|
||||
}
|
||||
],
|
||||
"to": {
|
||||
"address": {
|
||||
"administrative_regions": [
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
},
|
||||
{
|
||||
"coord": {
|
||||
"lat": "48.856609",
|
||||
"lon": "2.351499"
|
||||
},
|
||||
"id": "admin:fr:75056",
|
||||
"insee": "75056",
|
||||
"label": "Paris",
|
||||
"level": 8,
|
||||
"name": "Paris",
|
||||
"zip_code": ""
|
||||
}
|
||||
],
|
||||
"coord": {
|
||||
"lat": "48.8583736",
|
||||
"lon": "2.2922926"
|
||||
},
|
||||
"house_number": 69,
|
||||
"id": "2.2922926;48.8583736",
|
||||
"label": "69 Quai Branly (Paris)",
|
||||
"name": "Quai Branly"
|
||||
},
|
||||
"embedded_type": "address",
|
||||
"id": "2.2922926;48.8583736",
|
||||
"name": "69 Quai Branly (Paris)",
|
||||
"quality": 0
|
||||
},
|
||||
"type": "street_network"
|
||||
}
|
||||
],
|
||||
"status": "",
|
||||
"tags": [
|
||||
"walking",
|
||||
"ecologic"
|
||||
],
|
||||
"type": "best"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue