EV Chargers service (#219)

This commit is contained in:
Dominic Wong
2021-10-01 14:17:54 +01:00
committed by GitHub
parent 3c38d96881
commit f52cfba017
38 changed files with 14584 additions and 3 deletions

2
evchargers/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
evchargers

3
evchargers/Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM alpine
ADD evchargers /evchargers
ENTRYPOINT [ "/evchargers" ]

28
evchargers/Makefile Normal file
View File

@@ -0,0 +1,28 @@
GOPATH:=$(shell go env GOPATH)
.PHONY: init
init:
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
go get github.com/micro/micro/v3/cmd/protoc-gen-openapi
.PHONY: api
api:
protoc --openapi_out=. --proto_path=. proto/evchargers.proto
.PHONY: proto
proto:
protoc --proto_path=. --micro_out=. --go_out=:. proto/evchargers.proto
.PHONY: build
build:
go build -o evchargers *.go
.PHONY: test
test:
go test -v ./... -cover
.PHONY: docker
docker:
docker build . -t evchargers:latest

11
evchargers/README.md Normal file
View File

@@ -0,0 +1,11 @@
Find electric vehicle (EV) chargers wherever you go
# EV Chargers Service
This is the E(lectric) V(ehicle) chargers API. Search for EV chargers using
- location and distance
- bounding box
and filter the results based on connection type, operator, and more.
Powered by https://openchargemap.org/

643
evchargers/examples.json Normal file
View File

@@ -0,0 +1,643 @@
{
"search": [
{
"title": "Search by location",
"run_check": true,
"request": {
"location": {
"latitude": 51.53336351319885,
"longitude": -0.0252
},
"distance": 2000,
"max_results": 2
},
"response": {
"pois": [
{
"id": "121442",
"data_provider_id": "1",
"operator_id": "25",
"usage_type_id": "4",
"address": {
"location": {
"latitude": 51.539715,
"longitude": -0.02769
},
"title": "St Marks Gate",
"address_line_1": "Opposite 1 St Marks Gate",
"address_line_2": "",
"town": "London",
"state_or_province": "",
"access_comments": "",
"postcode": "E9 5HT",
"country_id": "1",
"country": {
"id": "1",
"title": "United Kingdom",
"iso_code": "GB",
"continent_code": "EU"
}
},
"connections": [
{
"connection_type_id": "1",
"reference": "",
"level": "2",
"amps": 16,
"voltage": 230,
"power": 4,
"current": "10",
"connection_type": {
"id": "1",
"title": "Type 1 (J1772)",
"formal_name": "SAE J1772-2009",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "25",
"reference": "",
"level": "2",
"amps": 32,
"voltage": 230,
"power": 7,
"current": "10",
"connection_type": {
"id": "25",
"title": "Type 2 (Socket Only)",
"formal_name": "IEC 62196-2 Type 2",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "1",
"reference": "",
"level": "2",
"amps": 16,
"voltage": 230,
"power": 4,
"current": "10",
"connection_type": {
"id": "1",
"title": "Type 1 (J1772)",
"formal_name": "SAE J1772-2009",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "25",
"reference": "",
"level": "2",
"amps": 32,
"voltage": 230,
"power": 7,
"current": "10",
"connection_type": {
"id": "25",
"title": "Type 2 (Socket Only)",
"formal_name": "IEC 62196-2 Type 2",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "1",
"reference": "",
"level": "2",
"amps": 16,
"voltage": 230,
"power": 4,
"current": "10",
"connection_type": {
"id": "1",
"title": "Type 1 (J1772)",
"formal_name": "SAE J1772-2009",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "25",
"reference": "",
"level": "2",
"amps": 32,
"voltage": 230,
"power": 7,
"current": "10",
"connection_type": {
"id": "25",
"title": "Type 2 (Socket Only)",
"formal_name": "IEC 62196-2 Type 2",
"is_discontinued": false,
"is_obsolete": false
}
}
],
"num_points": "3",
"cost": "£0.059/minute; min £1.18; other tariffs available",
"operator": {
"id": "25",
"title": "Source London",
"website": "http://www.sourcelondon.net/",
"comments": "Monthly post-payment membership system, with per-minute billing and optional lower tariffs in return for monthly subscription payment.",
"is_private_individual": false,
"contact_email": "membership@sourcelondon.net",
"phone_primary": "",
"phone_secondary": "",
"fault_report_email": "membership@sourcelondon.net"
},
"usage_type": {
"id": "4",
"title": "Public - Membership Required",
"is_pay_at_location": false,
"is_membership_required": true,
"is_access_key_required": true
}
},
{
"id": "189330",
"data_provider_id": "1",
"operator_id": "3296",
"usage_type_id": "5",
"address": {
"location": {
"latitude": 51.529694,
"longitude": -0.015285153
},
"title": "McDonald's Bow",
"address_line_1": "4 Payne Road",
"address_line_2": "",
"town": "London",
"state_or_province": "England",
"access_comments": "",
"postcode": "E3 2SP",
"country_id": "1",
"country": {
"id": "1",
"title": "United Kingdom",
"iso_code": "GB",
"continent_code": "EU"
}
},
"connections": [
{
"connection_type_id": "33",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "33",
"title": "CCS (Type 2)",
"formal_name": "IEC 62196-3 Configuration FF",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "2",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "2",
"title": "CHAdeMO",
"formal_name": "IEC 62196-3 Configuration AA",
"is_discontinued": false,
"is_obsolete": false
}
}
],
"num_points": "2",
"cost": "£0.40/kWh",
"operator": {
"id": "3296",
"title": "InstaVolt Ltd",
"website": "http://instavolt.co.uk/",
"comments": "Pay-as-you-go, charged per kWh, contactless bank card payment.",
"is_private_individual": false,
"contact_email": "",
"phone_primary": "",
"phone_secondary": "",
"fault_report_email": ""
},
"usage_type": {
"id": "5",
"title": "Public - Pay At Location",
"is_pay_at_location": true,
"is_membership_required": false,
"is_access_key_required": false
}
}
]
}
},
{
"title": "Search by bounding box",
"run_check": true,
"request": {
"box": {
"bottom_left": {
"latitude":51.52627543859447,
"longitude": -0.03635349400295168
},
"top_right": {
"latitude": 51.56717121807993,
"longitude":-0.002293530559768285
}
},
"max_results": 2
},
"response": {
"pois": [
{
"id": "189330",
"data_provider_id": "1",
"operator_id": "3296",
"usage_type_id": "5",
"address": {
"location": {
"latitude": 51.529694,
"longitude": -0.015285153
},
"title": "McDonald's Bow",
"address_line_1": "4 Payne Road",
"address_line_2": "",
"town": "London",
"state_or_province": "England",
"access_comments": "",
"postcode": "E3 2SP",
"country_id": "1",
"country": {
"id": "1",
"title": "United Kingdom",
"iso_code": "GB",
"continent_code": "EU"
}
},
"connections": [
{
"connection_type_id": "33",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "33",
"title": "CCS (Type 2)",
"formal_name": "IEC 62196-3 Configuration FF",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "2",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "2",
"title": "CHAdeMO",
"formal_name": "IEC 62196-3 Configuration AA",
"is_discontinued": false,
"is_obsolete": false
}
}
],
"num_points": "2",
"cost": "£0.40/kWh",
"operator": {
"id": "3296",
"title": "InstaVolt Ltd",
"website": "http://instavolt.co.uk/",
"comments": "Pay-as-you-go, charged per kWh, contactless bank card payment.",
"is_private_individual": false,
"contact_email": "",
"phone_primary": "",
"phone_secondary": "",
"fault_report_email": ""
},
"usage_type": {
"id": "5",
"title": "Public - Pay At Location",
"is_pay_at_location": true,
"is_membership_required": false,
"is_access_key_required": false
}
},
{
"id": "187765",
"data_provider_id": "18",
"operator_id": "3",
"usage_type_id": "1",
"address": {
"location": {
"latitude": 51.529995,
"longitude": -0.009934
},
"title": "Hunt's Lane",
"address_line_1": "Hunt's Lane",
"address_line_2": "",
"town": "London",
"state_or_province": "",
"access_comments": "",
"postcode": "E15 2QD",
"country_id": "1",
"country": {
"id": "1",
"title": "United Kingdom",
"iso_code": "GB",
"continent_code": "EU"
}
},
"connections": [
{
"connection_type_id": "25",
"reference": "0",
"level": "2",
"amps": 32,
"voltage": 230,
"power": 7,
"current": "10",
"connection_type": {
"id": "25",
"title": "Type 2 (Socket Only)",
"formal_name": "IEC 62196-2 Type 2",
"is_discontinued": false,
"is_obsolete": false
}
}
],
"num_points": "0",
"cost": "",
"operator": {
"id": "3",
"title": "POD Point (UK)",
"website": "http://www.pod-point.com/",
"comments": "Part of Groupe EDF",
"is_private_individual": false,
"contact_email": "enquiries@pod-point.com",
"phone_primary": "020 7247 4114",
"phone_secondary": "",
"fault_report_email": "enquiries@pod-point.com"
},
"usage_type": {
"id": "1",
"title": "Public",
"is_pay_at_location": false,
"is_membership_required": false,
"is_access_key_required": false
}
}
]
}
},
{
"title": "Search with filters. Fast chargers only",
"run_check": true,
"request": {
"location": {
"latitude": 51.53336351319885,
"longitude": -0.0252
},
"distance": 2000,
"max_results": 2,
"levels": [
"3"
]
},
"response": {
"pois": [
{
"id": "189330",
"data_provider_id": "1",
"operator_id": "3296",
"usage_type_id": "5",
"address": {
"location": {
"latitude": 51.529694,
"longitude": -0.015285153
},
"title": "McDonald's Bow",
"address_line_1": "4 Payne Road",
"address_line_2": "",
"town": "London",
"state_or_province": "England",
"access_comments": "",
"postcode": "E3 2SP",
"country_id": "1",
"country": {
"id": "1",
"title": "United Kingdom",
"iso_code": "GB",
"continent_code": "EU"
}
},
"connections": [
{
"connection_type_id": "33",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "33",
"title": "CCS (Type 2)",
"formal_name": "IEC 62196-3 Configuration FF",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "2",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "2",
"title": "CHAdeMO",
"formal_name": "IEC 62196-3 Configuration AA",
"is_discontinued": false,
"is_obsolete": false
}
}
],
"num_points": "2",
"cost": "£0.40/kWh",
"operator": {
"id": "3296",
"title": "InstaVolt Ltd",
"website": "http://instavolt.co.uk/",
"comments": "Pay-as-you-go, charged per kWh, contactless bank card payment.",
"is_private_individual": false,
"contact_email": "",
"phone_primary": "",
"phone_secondary": "",
"fault_report_email": ""
},
"usage_type": {
"id": "5",
"title": "Public - Pay At Location",
"is_pay_at_location": true,
"is_membership_required": false,
"is_access_key_required": false
}
},
{
"id": "127251",
"data_provider_id": "1",
"operator_id": "32",
"usage_type_id": "4",
"address": {
"location": {
"latitude": 51.543983,
"longitude": -0.01991
},
"title": "Queen Elizabeth Olympic Park",
"address_line_1": "Queen Elizabeth Olympic Park",
"address_line_2": "",
"town": "Copper Box Car Park",
"state_or_province": "London",
"access_comments": "",
"postcode": "E20 2ST",
"country_id": "1",
"country": {
"id": "1",
"title": "United Kingdom",
"iso_code": "GB",
"continent_code": "EU"
}
},
"connections": [
{
"connection_type_id": "25",
"reference": "",
"level": "2",
"amps": 32,
"voltage": 400,
"power": 22,
"current": "20",
"connection_type": {
"id": "25",
"title": "Type 2 (Socket Only)",
"formal_name": "IEC 62196-2 Type 2",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "2",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "2",
"title": "CHAdeMO",
"formal_name": "IEC 62196-3 Configuration AA",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "33",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 50,
"current": "30",
"connection_type": {
"id": "33",
"title": "CCS (Type 2)",
"formal_name": "IEC 62196-3 Configuration FF",
"is_discontinued": false,
"is_obsolete": false
}
},
{
"connection_type_id": "1036",
"reference": "",
"level": "3",
"amps": 0,
"voltage": 0,
"power": 43,
"current": "20",
"connection_type": {
"id": "1036",
"title": "Type 2 (Tethered Connector) ",
"formal_name": "IEC 62196-2",
"is_discontinued": false,
"is_obsolete": false
}
}
],
"num_points": "2",
"cost": "Inclusive; for Polar Plus subscription members only",
"operator": {
"id": "32",
"title": "BP Pulse",
"website": "https://www.bppulse.co.uk/",
"comments": "Formerly known as Polar Network, or BP Chargemaster",
"is_private_individual": false,
"contact_email": "polarenquiries@chargemasterplc.com",
"phone_primary": "01582 400331",
"phone_secondary": "",
"fault_report_email": "polarenquiries@chargemasterplc.com"
},
"usage_type": {
"id": "4",
"title": "Public - Membership Required",
"is_pay_at_location": false,
"is_membership_required": true,
"is_access_key_required": true
}
}
]
}
}
],
"referenceData": [
{
"title": "Get reference data",
"run_check": true,
"request": {
},
"response": {
"charger_types": [],
"connection_types": [],
"current_types": [],
"countries": [],
"data_providers": [],
"operators": [],
"status_types": [],
"submission_status_types": [],
"usage_types": [],
"usage_types": [],
"user_comment_types": [],
"checkin_status_types": []
}
}
]
}

2
evchargers/generate.go Normal file
View File

@@ -0,0 +1,2 @@
package main
//go:generate make proto

100
evchargers/handler/data.go Normal file
View File

@@ -0,0 +1,100 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/micro/micro/v3/service/logger"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func (e *Evchargers) loadPOIData(r io.Reader) (int, error) {
logger.Infof("Loading reference data")
dec := json.NewDecoder(r)
t, err := dec.Token()
if err != nil {
return 0, err
}
d, ok := t.(json.Delim)
if !ok || d.String() != "[" {
return 0, fmt.Errorf("unexpected token %v %+v", ok, t)
}
ctx := context.Background()
count := 0
for dec.More() {
// process each item in json array and insert into mongodb
var p Poi
if err := dec.Decode(&p); err != nil {
return 0, fmt.Errorf("error unmarshalling charger %s", err)
}
if len(p.SpatialPosition.Type) == 0 {
// blank so reconstruct
p.SpatialPosition.Type = "Point"
// long, lat not lat, long
p.SpatialPosition.Coordinates = []float64{p.Address.Longitude, p.Address.Latitude}
}
t := true
_, err := e.mdb.Database("ocm").Collection("poi").ReplaceOne(ctx, bson.D{bson.E{"ID", p.ID}}, p, &options.ReplaceOptions{Upsert: &t})
if err != nil {
return 0, err
}
count++
}
return count, nil
}
func (e *Evchargers) loadRefData(r io.Reader) error {
dec := json.NewDecoder(r)
var rd ReferenceData
if err := dec.Decode(&rd); err != nil {
return err
}
ctx := context.Background()
t := true
_, err := e.mdb.Database("ocm").Collection("reference").ReplaceOne(ctx, bson.D{bson.E{"_id", 1}}, rd, &options.ReplaceOptions{Upsert: &t})
if err != nil {
return err
}
return nil
}
func (e *Evchargers) refreshDataFromSource() {
start := time.Now()
logger.Infof("Refreshing data")
logger.Infof("Retrieving poi data")
rsp, err := http.Get(fmt.Sprintf("https://api.openchargemap.io/v3/poi/?output=json&key=%s&maxresults=10000000", e.conf.OCMKey))
if err != nil {
logger.Errorf("Error refreshing data %s", err)
return
}
defer rsp.Body.Close()
c, err := e.loadPOIData(rsp.Body)
if err != nil {
logger.Errorf("Error loading data %s", err)
return
}
logger.Infof("Updated %v items of POI data. Took %s", c, time.Since(start))
start = time.Now()
logger.Infof("Retrieving ref data")
rsp2, err := http.Get(fmt.Sprintf("https://api.openchargemap.io/v3/referencedata/?output=json&key=%s", e.conf.OCMKey))
if err != nil {
logger.Errorf("Error refreshing reference data %s", err)
return
}
defer rsp2.Body.Close()
if err := e.loadRefData(rsp2.Body); err != nil {
logger.Errorf("Error loading reference data %s", err)
return
}
logger.Infof("Updated ref data. Took %s", time.Since(start))
}

View File

@@ -0,0 +1,55 @@
package handler
import (
"context"
"io"
"os"
"testing"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func TestDataLoad(t *testing.T) {
t.SkipNow()
s := Evchargers{}
opts := []*options.ClientOptions{options.Client().ApplyURI("mongodb://127.0.0.1:27017/ocm")}
var err error
s.mdb, err = mongo.Connect(context.Background(), opts...)
if err != nil {
t.Fatalf("Error connecting to mongo %s", err)
}
var r io.Reader
r, err = os.Open("test.json")
if err != nil {
t.Fatalf("Error opening test data %s", err)
}
c, err := s.loadPOIData(r)
if err != nil {
t.Fatalf("Err loading data %s", err)
}
if c != 2 {
t.Errorf("Incorrect number of records %d", c)
}
}
func TestRefDataLoad(t *testing.T) {
t.SkipNow()
s := Evchargers{}
opts := []*options.ClientOptions{options.Client().ApplyURI("mongodb://127.0.0.1:27017/ocm")}
var err error
s.mdb, err = mongo.Connect(context.Background(), opts...)
if err != nil {
t.Fatalf("Error connecting to mongo %s", err)
}
var r io.Reader
r, err = os.Open("test-reference.json")
if err != nil {
t.Fatalf("Error opening test data %s", err)
}
err = s.loadRefData(r)
if err != nil {
t.Fatalf("Err loading data %s", err)
}
}

View File

@@ -0,0 +1,438 @@
package handler
import (
"context"
"io/ioutil"
"os"
"strconv"
"time"
"github.com/micro/micro/v3/service/errors"
"github.com/robfig/cron/v3"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/micro/micro/v3/service/config"
log "github.com/micro/micro/v3/service/logger"
evchargers "github.com/micro/services/evchargers/proto"
)
const (
defaultDistance = int64(5000) // 5km
)
var (
sphereIndexVersion = int32(3)
)
type Evchargers struct {
conf conf
mdb *mongo.Client
}
type conf struct {
MongoHost string `json:"mongo_host"`
CaCrt string `json:"ca_crt"`
OCMKey string `json:"ocm_key"`
}
func New() *Evchargers {
val, err := config.Get("micro.evchargers")
if err != nil {
log.Fatalf("Failed to load config")
}
var conf conf
if err := val.Scan(&conf); err != nil {
log.Fatalf("Failed to load config")
}
if len(conf.MongoHost) == 0 {
log.Fatalf("Missing mongodb host")
}
if len(conf.CaCrt) > 0 {
// write the cert to file
if err := ioutil.WriteFile(os.TempDir()+"/mongo.crt", []byte(conf.CaCrt), 0644); err != nil {
log.Fatalf("Failed to write crt file for mongodb %s", err)
}
}
opts := []*options.ClientOptions{options.Client().ApplyURI(conf.MongoHost)}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, opts...)
if err != nil {
log.Fatalf("Failed to connect to mongo db %s", err)
}
ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel2()
if err := client.Ping(ctx2, nil); err != nil {
log.Fatalf("Failed to ping mongo db %s", err)
}
// make sure the indexes are set up
_, err = client.Database("ocm").Collection("poi").Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{ // bounding box queries
Keys: bson.D{{"SpatialPosition.coordinates", "2dsphere"}},
Options: &options.IndexOptions{
SphereVersion: &sphereIndexVersion,
},
},
{ // distance queries
Keys: bson.D{{"SpatialPosition", "2dsphere"}},
Options: &options.IndexOptions{
SphereVersion: &sphereIndexVersion,
},
},
{
Keys: bson.M{"DateCreated": -1},
},
{
Keys: bson.M{"DateLastStatusUpdate": -1},
},
{
Keys: bson.M{"ID": -1},
},
})
if err != nil {
log.Fatalf("Failed to craete indexes %s", err)
}
ev := &Evchargers{conf: conf, mdb: client}
if len(conf.OCMKey) > 0 {
c := cron.New()
// 4am every Sunday for refresh
c.AddFunc("0 4 * * 0", ev.refreshDataFromSource)
c.Start()
}
return ev
}
func (e *Evchargers) Search(ctx context.Context, request *evchargers.SearchRequest, response *evchargers.SearchResponse) error {
toInt := func(in []string) []interface{} {
res := make([]interface{}, len(in))
for i, v := range in {
res[i], _ = strconv.Atoi(v)
}
return res
}
filters := bson.D{}
if len(request.ConnectionTypes) > 0 {
vals := bson.A{}
vals = append(vals, toInt(request.ConnectionTypes)...)
filters = append(filters, bson.E{"Connections.ConnectionTypeID", bson.D{{"$in", vals}}})
}
if request.Location != nil {
distance := defaultDistance
if request.Distance > 0 {
distance = request.Distance
}
filters = append(filters, bson.E{"SpatialPosition", bson.M{"$nearSphere": bson.M{"$geometry": bson.M{
"type": "Point",
"coordinates": []float64{float64(request.Location.Longitude), float64(request.Location.Latitude)},
},
"$maxDistance": distance,
},
}})
} else if request.Box != nil && request.Box.BottomLeft != nil {
filters = append(filters, bson.E{"SpatialPosition.coordinates", bson.M{"$geoWithin": bson.M{"$box": bson.A{
[]float32{request.Box.BottomLeft.Longitude, request.Box.BottomLeft.Latitude},
[]float32{request.Box.TopRight.Longitude, request.Box.TopRight.Latitude},
}}}})
}
if len(request.CountryId) > 0 {
i, _ := strconv.Atoi(request.CountryId)
filters = append(filters, bson.E{"AddressInfo.CountryID", i})
}
if len(request.Levels) > 0 {
vals := bson.A{}
vals = append(vals, toInt(request.Levels)...)
filters = append(filters, bson.E{"Connections.LevelID", bson.D{{"$in", vals}}})
}
if request.MinPower > 0 {
filters = append(filters, bson.E{"Connections.PowerKW", bson.D{{"$gte", request.MinPower}}})
}
if len(request.Operators) > 0 {
vals := bson.A{}
vals = append(vals, toInt(request.Operators)...)
filters = append(filters, bson.E{"OperatorID", bson.D{{"$in", vals}}})
}
if len(request.UsageTypes) > 0 {
vals := bson.A{}
vals = append(vals, toInt(request.UsageTypes)...)
filters = append(filters, bson.E{"UsageTypeID", bson.D{{"$in", vals}}})
}
maxLim := int64(100)
max := options.FindOptions{
Limit: &maxLim,
}
if request.MaxResults > 0 {
max.Limit = &request.MaxResults
}
crs, err := e.mdb.Database("ocm").Collection("poi").Find(ctx, filters, &max)
if err != nil {
log.Errorf("Error querying %s", err)
return errors.InternalServerError("evchargers.search", "Failed to query ev chargers")
}
defer crs.Close(ctx)
for crs.Next(ctx) {
var result Poi
if err := crs.Decode(&result); err != nil {
log.Errorf("Error decoding result %s", err)
return errors.InternalServerError("evchargers.search", "Failed to query ev chargers")
}
poi := &evchargers.Poi{
Id: strconv.Itoa(int(result.ID)),
DataProviderId: strconv.Itoa(int(result.DataProviderID)),
OperatorId: strconv.Itoa(int(result.OperatorID)),
UsageTypeId: strconv.Itoa(int(result.UsageTypeID)),
Address: &evchargers.Address{
Location: &evchargers.Coordinates{
Latitude: float32(result.Address.Latitude),
Longitude: float32(result.Address.Longitude),
},
Title: result.Address.Title,
AddressLine_1: result.Address.AddressLine1,
AddressLine_2: result.Address.AddressLine2,
Town: result.Address.Town,
StateOrProvince: result.Address.StateOrProvince,
AccessComments: result.Address.AccessComments,
Postcode: result.Address.Postcode,
CountryId: strconv.Itoa(int(result.Address.CountryID)),
},
Connections: marshalConnections(result.Connections),
NumPoints: int64(result.NumberOfPoints),
Cost: result.Cost,
}
if true { // verbose
poi.Operator = marshalOperator(result.OperatorInfo)
poi.UsageType = marshalUsageType(result.UsageType)
poi.Address.Country = marshalCountry(result.Address.Country)
}
response.Pois = append(response.Pois, poi)
}
return nil
}
func marshalCountry(in Country) *evchargers.Country {
return &evchargers.Country{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
IsoCode: in.ISOCode,
ContinentCode: in.ContinentCode,
}
}
func marshalConnections(in []Connection) []*evchargers.Connection {
res := make([]*evchargers.Connection, len(in))
for i, v := range in {
res[i] = &evchargers.Connection{
ConnectionTypeId: strconv.Itoa(int(v.TypeID)),
ConnectionType: &evchargers.ConnectionType{
Id: strconv.Itoa(int(v.Type.ID)),
Title: v.Type.Title,
FormalName: v.Type.FormalName,
IsDiscontinued: v.Type.IsDiscontinued,
IsObsolete: v.Type.IsObsolete,
},
Reference: v.Reference,
Level: strconv.Itoa(int(v.LevelID)),
Amps: float32(v.Amps),
Voltage: float32(v.Voltage),
Power: float32(v.Power),
Current: strconv.Itoa(int(v.CurrentTypeID)),
}
}
return res
}
func marshalDataProvider(in DataProvider) *evchargers.DataProvider {
return &evchargers.DataProvider{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
Website: in.WebsiteURL,
Comments: in.Comments,
DataProviderStatusType: marshalDataProviderStatus(in.DataProviderStatus),
License: in.License,
}
}
func marshalDataProviderStatus(in DataProviderStatus) *evchargers.DataProviderStatusType {
return &evchargers.DataProviderStatusType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
IsProviderEnabled: in.IsProviderEnabled,
}
}
func marshalOperator(in Operator) *evchargers.Operator {
return &evchargers.Operator{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
Website: in.WebsiteURL,
Comments: in.Comments,
IsPrivateIndividual: in.IsPrivateIndividual,
ContactEmail: in.ContactEmail,
PhonePrimary: in.PhonePrimary,
PhoneSecondary: in.PhoneSecondary,
FaultReportEmail: in.FaultReportEmail,
}
}
func marshalUsageType(in UsageType) *evchargers.UsageType {
return &evchargers.UsageType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
IsPayAtLocation: in.IsPayAtLocation,
IsMembershipRequired: in.IsMembershipRequired,
IsAccessKeyRequired: in.IsAccessKeyRequired,
}
}
func marshalCheckinStatusType(in CheckinStatusType) *evchargers.CheckinStatusType {
return &evchargers.CheckinStatusType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
IsPositive: in.IsPositive,
IsAutomated: in.IsAutomatedCheckin,
}
}
func marshalUserCommentType(in UserCommentType) *evchargers.UserCommentType {
return &evchargers.UserCommentType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
}
}
func marshalStatusType(in StatusType) *evchargers.StatusType {
return &evchargers.StatusType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
IsOperational: in.IsOperational,
}
}
func marshalCurrentType(in CurrentType) *evchargers.CurrentType {
return &evchargers.CurrentType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
Description: in.Description,
}
}
func marshalConnectionType(in ConnectionType) *evchargers.ConnectionType {
return &evchargers.ConnectionType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
FormalName: in.FormalName,
IsDiscontinued: in.IsDiscontinued,
IsObsolete: in.IsObsolete,
}
}
func marshalChargerType(in ChargerType) *evchargers.ChargerType {
return &evchargers.ChargerType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
Comments: in.Comments,
IsFastChargeCapable: in.IsFastChargeCapable,
}
}
func marshalSubmissionStatusType(in SubmissionStatusType) *evchargers.SubmissionStatusType {
return &evchargers.SubmissionStatusType{
Id: strconv.Itoa(int(in.ID)),
Title: in.Title,
IsLive: in.IsLive,
}
}
func (e *Evchargers) ReferenceData(ctx context.Context, request *evchargers.ReferenceDataRequest, response *evchargers.ReferenceDataResponse) error {
res := e.mdb.Database("ocm").Collection("reference").FindOne(ctx, bson.D{})
if res.Err() != nil {
log.Errorf("Error retrieving ref data %s", res.Err())
return errors.InternalServerError("evchargers.referencedata", "Error retrieving reference data")
}
var r ReferenceData
if err := res.Decode(&r); err != nil {
log.Errorf("Error decoding ref data %s", err)
return errors.InternalServerError("evchargers.referencedata", "Error retrieving reference data")
}
dps := make([]*evchargers.DataProvider, len(r.DataProviders))
for i, dp := range r.DataProviders {
dps[i] = marshalDataProvider(dp)
}
response.DataProviders = dps
cs := make([]*evchargers.Country, len(r.Countries))
for i, c := range r.Countries {
cs[i] = marshalCountry(c)
}
response.Countries = cs
cst := make([]*evchargers.CheckinStatusType, len(r.CheckinStatusTypes))
for i, v := range r.CheckinStatusTypes {
cst[i] = marshalCheckinStatusType(v)
}
response.CheckinStatusTypes = cst
uct := make([]*evchargers.UserCommentType, len(r.UserCommentTypes))
for i, v := range r.UserCommentTypes {
uct[i] = marshalUserCommentType(v)
}
response.UserCommentTypes = uct
st := make([]*evchargers.StatusType, len(r.StatusTypes))
for i, v := range r.StatusTypes {
st[i] = marshalStatusType(v)
}
response.StatusTypes = st
ut := make([]*evchargers.UsageType, len(r.UsageTypes))
for i, v := range r.UsageTypes {
ut[i] = marshalUsageType(v)
}
response.UsageTypes = ut
ct := make([]*evchargers.CurrentType, len(r.CurrentTypes))
for i, v := range r.CurrentTypes {
ct[i] = marshalCurrentType(v)
}
response.CurrentTypes = ct
connt := make([]*evchargers.ConnectionType, len(r.ConnectionTypes))
for i, v := range r.ConnectionTypes {
connt[i] = marshalConnectionType(v)
}
response.ConnectionTypes = connt
chrgt := make([]*evchargers.ChargerType, len(r.ChargerTypes))
for i, v := range r.ChargerTypes {
chrgt[i] = marshalChargerType(v)
}
response.ChargerTypes = chrgt
ops := make([]*evchargers.Operator, len(r.Operators))
for i, v := range r.Operators {
ops[i] = marshalOperator(v)
}
response.Operators = ops
sst := make([]*evchargers.SubmissionStatusType, len(r.SubmissionStatusTypes))
for i, v := range r.SubmissionStatusTypes {
sst[i] = marshalSubmissionStatusType(v)
}
response.SubmissionStatusTypes = sst
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,389 @@
[
{
"DataProvider": {
"WebsiteURL": "http://openchargemap.org",
"Comments": null,
"DataProviderStatusType": {
"IsProviderEnabled": true,
"ID": 1,
"Title": "Manual Data Entry"
},
"IsRestrictedEdit": false,
"IsOpenDataLicensed": true,
"IsApprovedImport": true,
"License": "Licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)",
"DateLastImported": null,
"ID": 1,
"Title": "Open Charge Map Contributors"
},
"OperatorInfo": {
"WebsiteURL": "https://evcharge.online/",
"Comments": "Rolec. Pre-payment, minimum topup £5.00",
"PhonePrimaryContact": null,
"PhoneSecondaryContact": null,
"IsPrivateIndividual": false,
"AddressInfo": null,
"BookingURL": null,
"ContactEmail": "Contact@power-portal.co.uk",
"FaultReportEmail": "Contact@power-portal.co.uk",
"IsRestrictedEdit": false,
"ID": 3295,
"Title": "evcharge.online"
},
"UsageType": {
"IsPayAtLocation": false,
"IsMembershipRequired": false,
"IsAccessKeyRequired": false,
"ID": 6,
"Title": "Private - For Staff, Visitors or Customers"
},
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"SubmissionStatus": {
"IsLive": true,
"ID": 200,
"Title": "Submission Published"
},
"UserComments": null,
"PercentageSimilarity": null,
"MediaItems": null,
"IsRecentlyVerified": true,
"DateLastVerified": "2021-09-27T16:41:00Z",
"ID": 189420,
"UUID": "E8B7D92B-8E36-43AB-909C-48A87A4BA279",
"ParentChargePointID": null,
"DataProviderID": 1,
"DataProvidersReference": null,
"OperatorID": 3295,
"OperatorsReference": null,
"UsageTypeID": 6,
"UsageCost": "Free",
"AddressInfo": {
"ID": 189779,
"Title": "Agrovista Allscott ",
"AddressLine1": "Agrovista Amenity",
"AddressLine2": "Walcot",
"Town": "Wrockwardine",
"StateOrProvince": "Telford and Wrekin",
"Postcode": "TF6 5DY",
"CountryID": 1,
"Country": {
"ISOCode": "GB",
"ContinentCode": "EU",
"ID": 1,
"Title": "United Kingdom"
},
"Latitude": 52.70780288629825,
"Longitude": -2.5934020726602967,
"ContactTelephone1": null,
"ContactTelephone2": null,
"ContactEmail": null,
"AccessComments": null,
"RelatedURL": null,
"Distance": null,
"DistanceUnit": 0
},
"Connections": [
{
"ID": 306355,
"ConnectionTypeID": 25,
"ConnectionType": {
"FormalName": "IEC 62196-2 Type 2",
"IsDiscontinued": false,
"IsObsolete": false,
"ID": 25,
"Title": "Type 2 (Socket Only)"
},
"Reference": "UKEV6095",
"StatusTypeID": 50,
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"LevelID": 2,
"Level": {
"Comments": "Over 2 kW, usually non-domestic socket type",
"IsFastChargeCapable": false,
"ID": 2,
"Title": "Level 2 : Medium (Over 2kW)"
},
"Amps": 32,
"Voltage": 400,
"PowerKW": 22.0,
"CurrentTypeID": 20,
"CurrentType": {
"Description": "Alternating Current - Three Phase",
"ID": 20,
"Title": "AC (Three-Phase)"
},
"Quantity": 1,
"Comments": null
},
{
"ID": 306356,
"ConnectionTypeID": 25,
"ConnectionType": {
"FormalName": "IEC 62196-2 Type 2",
"IsDiscontinued": false,
"IsObsolete": false,
"ID": 25,
"Title": "Type 2 (Socket Only)"
},
"Reference": "UKEV6096",
"StatusTypeID": 50,
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"LevelID": 2,
"Level": {
"Comments": "Over 2 kW, usually non-domestic socket type",
"IsFastChargeCapable": false,
"ID": 2,
"Title": "Level 2 : Medium (Over 2kW)"
},
"Amps": 32,
"Voltage": 400,
"PowerKW": 22.0,
"CurrentTypeID": 20,
"CurrentType": {
"Description": "Alternating Current - Three Phase",
"ID": 20,
"Title": "AC (Three-Phase)"
},
"Quantity": 1,
"Comments": null
},
{
"ID": 306357,
"ConnectionTypeID": 25,
"ConnectionType": {
"FormalName": "IEC 62196-2 Type 2",
"IsDiscontinued": false,
"IsObsolete": false,
"ID": 25,
"Title": "Type 2 (Socket Only)"
},
"Reference": "UKEV6097",
"StatusTypeID": 50,
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"LevelID": 2,
"Level": {
"Comments": "Over 2 kW, usually non-domestic socket type",
"IsFastChargeCapable": false,
"ID": 2,
"Title": "Level 2 : Medium (Over 2kW)"
},
"Amps": 32,
"Voltage": 400,
"PowerKW": 22.0,
"CurrentTypeID": 20,
"CurrentType": {
"Description": "Alternating Current - Three Phase",
"ID": 20,
"Title": "AC (Three-Phase)"
},
"Quantity": 1,
"Comments": null
},
{
"ID": 306358,
"ConnectionTypeID": 25,
"ConnectionType": {
"FormalName": "IEC 62196-2 Type 2",
"IsDiscontinued": false,
"IsObsolete": false,
"ID": 25,
"Title": "Type 2 (Socket Only)"
},
"Reference": "UKEV6098",
"StatusTypeID": 50,
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"LevelID": 2,
"Level": {
"Comments": "Over 2 kW, usually non-domestic socket type",
"IsFastChargeCapable": false,
"ID": 2,
"Title": "Level 2 : Medium (Over 2kW)"
},
"Amps": 32,
"Voltage": 400,
"PowerKW": 22.0,
"CurrentTypeID": 20,
"CurrentType": {
"Description": "Alternating Current - Three Phase",
"ID": 20,
"Title": "AC (Three-Phase)"
},
"Quantity": 1,
"Comments": null
}
],
"NumberOfPoints": 4,
"GeneralComments": null,
"DatePlanned": null,
"DateLastConfirmed": null,
"StatusTypeID": 50,
"DateLastStatusUpdate": "2021-09-27T16:41:00Z",
"MetadataValues": null,
"DataQualityLevel": 1,
"DateCreated": "2021-09-27T16:41:00Z",
"SubmissionStatusTypeID": 200
},
{
"DataProvider": {
"WebsiteURL": "http://openchargemap.org",
"Comments": null,
"DataProviderStatusType": {
"IsProviderEnabled": true,
"ID": 1,
"Title": "Manual Data Entry"
},
"IsRestrictedEdit": false,
"IsOpenDataLicensed": true,
"IsApprovedImport": true,
"License": "Licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)",
"DateLastImported": null,
"ID": 1,
"Title": "Open Charge Map Contributors"
},
"OperatorInfo": {
"WebsiteURL": null,
"Comments": null,
"PhonePrimaryContact": null,
"PhoneSecondaryContact": null,
"IsPrivateIndividual": null,
"AddressInfo": null,
"BookingURL": null,
"ContactEmail": null,
"FaultReportEmail": null,
"IsRestrictedEdit": null,
"ID": 1,
"Title": "(Unknown Operator)"
},
"UsageType": {
"IsPayAtLocation": null,
"IsMembershipRequired": null,
"IsAccessKeyRequired": null,
"ID": 1,
"Title": "Public"
},
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"SubmissionStatus": {
"IsLive": true,
"ID": 200,
"Title": "Submission Published"
},
"UserComments": null,
"PercentageSimilarity": null,
"MediaItems": null,
"IsRecentlyVerified": true,
"DateLastVerified": "2021-09-27T15:13:00Z",
"ID": 189419,
"UUID": "2A489FF2-CF7B-41D8-979D-08A9A251568C",
"ParentChargePointID": null,
"DataProviderID": 1,
"DataProvidersReference": null,
"OperatorID": 1,
"OperatorsReference": null,
"UsageTypeID": 1,
"UsageCost": "FREE",
"AddressInfo": {
"ID": 189778,
"Title": "CRMU EV Station",
"AddressLine1": "Main Street",
"AddressLine2": null,
"Town": "Coon Rapids",
"StateOrProvince": "Iowa",
"Postcode": "50059",
"CountryID": 2,
"Country": {
"ISOCode": "US",
"ContinentCode": "NA",
"ID": 2,
"Title": "United States"
},
"Latitude": 41.870824375016525,
"Longitude": -94.67421942978399,
"ContactTelephone1": null,
"ContactTelephone2": null,
"ContactEmail": null,
"AccessComments": null,
"RelatedURL": null,
"Distance": null,
"DistanceUnit": 0
},
"Connections": [
{
"ID": 306345,
"ConnectionTypeID": 1,
"ConnectionType": {
"FormalName": "SAE J1772-2009",
"IsDiscontinued": null,
"IsObsolete": null,
"ID": 1,
"Title": "Type 1 (J1772)"
},
"Reference": null,
"StatusTypeID": 50,
"StatusType": {
"IsOperational": true,
"IsUserSelectable": true,
"ID": 50,
"Title": "Operational"
},
"LevelID": 2,
"Level": {
"Comments": "Over 2 kW, usually non-domestic socket type",
"IsFastChargeCapable": false,
"ID": 2,
"Title": "Level 2 : Medium (Over 2kW)"
},
"Amps": 30,
"Voltage": 239,
"PowerKW": 6.6,
"CurrentTypeID": 10,
"CurrentType": {
"Description": "Alternating Current - Single Phase",
"ID": 10,
"Title": "AC (Single-Phase)"
},
"Quantity": 2,
"Comments": null
}
],
"NumberOfPoints": 2,
"GeneralComments": null,
"DatePlanned": null,
"DateLastConfirmed": null,
"StatusTypeID": 50,
"DateLastStatusUpdate": "2021-09-27T16:06:00Z",
"MetadataValues": null,
"DataQualityLevel": 1,
"DateCreated": "2021-09-27T15:13:00Z",
"SubmissionStatusTypeID": 200
}
]

157
evchargers/handler/types.go Normal file
View File

@@ -0,0 +1,157 @@
package handler
type Poi struct {
ID int32 `bson:"ID" json:"ID"`
DataProviderID int32 `bson:"DataProviderID" json:"DataProviderID"`
DataProvider DataProvider `bson:"DataProvider" json:"DataProvider"`
OperatorID int32 `bson:"OperatorID" json:"OperatorID"`
OperatorInfo Operator `bson:"OperatorInfo" json:"OperatorInfo"`
UsageTypeID int32 `bson:"UsageTypeID" json:"UsageTypeID"`
UsageType UsageType `bson:"UsageType" json:"UsageType"`
Cost string `bson:"UsageCost" json:"UsageCost"`
Address Address `bson:"AddressInfo" json:"AddressInfo"`
Connections []Connection `bson:"Connections" json:"Connections"`
NumberOfPoints int32 `bson:"NumberOfPoints" json:"NumberOfPoints"`
GeneralComments string `bson:"GeneralComments" json:"GeneralComments"`
StatusTypeID int32 `bson:"StatusTypeID" json:"StatusTypeID"`
StatusType StatusType `bson:"StatusType" json:"StatusType"`
SpatialPosition Position `bson:"SpatialPosition" json:"SpatialPosition"`
}
type Position struct {
Type string `bson:"type" json:"type"`
Coordinates []float64 `bson:"coordinates" json:"coordinates"`
}
type Address struct {
Title string `bson:"Title" json:"Title"`
Latitude float64 `bson:"Latitude" json:"Latitude"`
Longitude float64 `bson:"Longitude" json:"Longitude"`
AddressLine1 string `bson:"AddressLine1" json:"AddressLine1"`
AddressLine2 string `bson:"AddressLine2" json:"AddressLine2"`
Town string `bson:"Town" json:"Town"`
StateOrProvince string `bson:"StateOrProvince" json:"StateOrProvince"`
AccessComments string `bson:"AccessComments" json:"AccessComments"`
Postcode string `bson:"Postcode" json:"Postcode"`
CountryID int32 `bson:"CountryID" json:"CountryID"`
Country Country `bson:"Country" json:"Country"`
}
type Country struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
ISOCode string `bson:"ISOCode" json:"ISOCode"`
ContinentCode string `bson:"ContinentCode" json:"ContinentCode"`
}
type Connection struct {
TypeID int32 `bson:"ConnectionTypeID" json:"ConnectionTypeID"`
Type ConnectionType `bson:"ConnectionType" json:"ConnectionType"`
StatusTypeID int32 `bson:"StatusTypeID" json:"StatusTypeID"`
StatusType StatusType `bson:"StatusType" json:"StatusType"`
LevelID int32 `bson:"LevelID" json:"LevelID"`
Level ChargerType `bson:"Level" json:"Level"`
Amps float64 `bson:"Amps" json:"Amps"`
Voltage float64 `bson:"Voltage" json:"Voltage"`
Power float64 `bson:"PowerKW" json:"PowerKW"`
CurrentTypeID int32 `bson:"CurrentTypeID" json:"CurrentTypeID"`
CurrentType CurrentType `bson:"CurrentType" json:"CurrentType"`
Quantity int32 `bson:"Quantity" json:"Quantity"`
Reference string `bson:"Reference" json:"Reference"`
}
type ChargerType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
Comments string `bson:"Comments" json:"Comments"`
IsFastChargeCapable bool `bson:"IsFastChargeCapable" json:"IsFastChargeCapable"`
}
type CurrentType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
Description string `bson:"Description" json:"Description"`
}
type ConnectionType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
FormalName string `bson:"FormalName" json:"FormalName"`
IsDiscontinued bool `bson:"IsDiscontinued" json:"IsDiscontinued"`
IsObsolete bool `bson:"IsObsolete" json:"IsObsolete"`
}
type DataProvider struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
WebsiteURL string `bson:"WebsiteURL" json:"WebsiteURL"`
Comments string `bson:"Comments" json:"Comments"`
DataProviderStatus DataProviderStatus `bson:"DataProviderStatusType" json:"DataProviderStatusType"`
IsOpenDataLicensed bool `bson:"IsOpenDataLicensed" json:"IsOpenDataLicensed"`
License string `bson:"License" json:"License"`
}
type DataProviderStatus struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
IsProviderEnabled bool `bson:"IsProviderEnabled" json:"IsProviderEnabled"`
}
type Operator struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
WebsiteURL string `bson:"WebsiteURL" json:"WebsiteURL"`
Comments string `bson:"Comments" json:"Comments"`
PhonePrimary string `bson:"PhonePrimaryContact" json:"PhonePrimaryContact"`
PhoneSecondary string `bson:"PhoneSecondaryContact" json:"PhoneSecondaryContact"`
IsPrivateIndividual bool `bson:"IsPrivateIndividual" json:"IsPrivateIndividual"`
ContactEmail string `bson:"ContactEmail" json:"ContactEmail"`
FaultReportEmail string `bson:"FaultReportEmail" json:"FaultReportEmail"`
}
type UsageType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
IsPayAtLocation bool `bson:"IsPayAtLocation" json:"IsPayAtLocation"`
IsMembershipRequired bool `bson:"IsMembershipRequired" json:"IsMembershipRequired"`
IsAccessKeyRequired bool `bson:"IsAccessKeyRequired" json:"IsAccessKeyRequired"`
}
type StatusType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
IsUsageSelectable bool `bson:"IsUsageSelectable" json:"IsUsageSelectable"`
IsOperational bool `bson:"IsOperational" json:"IsOperational"`
}
type UserCommentType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
}
type CheckinStatusType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
IsPositive bool `bson:"IsPositive" json:"IsPositive"`
IsAutomatedCheckin bool `bson:"IsAutomatedCheckin" json:"IsAutomatedCheckin"`
}
type ReferenceData struct {
ChargerTypes []ChargerType `bson:"ChargerTypes" json:"ChargerTypes"`
ConnectionTypes []ConnectionType `bson:"ConnectionTypes" json:"ConnectionTypes"`
CurrentTypes []CurrentType `bson:"CurrentTypes" json:"CurrentTypes"`
Countries []Country `bson:"Countries" json:"Countries"`
DataProviders []DataProvider `bson:"DataProviders" json:"DataProviders"`
Operators []Operator `bson:"Operators" json:"Operators"`
StatusTypes []StatusType `bson:"StatusTypes" json:"StatusTypes"`
UsageTypes []UsageType `bson:"UsageTypes" json:"UsageTypes"`
UserCommentTypes []UserCommentType `bson:"UserCommentTypes" json:"UserCommentTypes"`
CheckinStatusTypes []CheckinStatusType `bson:"CheckinStatusTypes" json:"CheckinStatusTypes"`
SubmissionStatusTypes []SubmissionStatusType `bson:"SubmissionStatusTypes" json:"SubmissionStatusTypes"`
}
type SubmissionStatusType struct {
ID int32 `bson:"ID" json:"ID"`
Title string `bson:"Title" json:"Title"`
IsLive bool `bson:"IsLive" json:"IsLive"`
}

25
evchargers/main.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"github.com/micro/services/evchargers/handler"
pb "github.com/micro/services/evchargers/proto"
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/logger"
)
func main() {
// Create service
srv := service.New(
service.Name("evchargers"),
service.Version("latest"),
)
// Register handler
pb.RegisterEvchargersHandler(srv.Server(), handler.New())
// Run service
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
}

1
evchargers/micro.mu Normal file
View File

@@ -0,0 +1 @@
service evchargers

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,110 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: proto/evchargers.proto
package evchargers
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
api "github.com/micro/micro/v3/service/api"
client "github.com/micro/micro/v3/service/client"
server "github.com/micro/micro/v3/service/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for Evchargers service
func NewEvchargersEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Evchargers service
type EvchargersService interface {
Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error)
ReferenceData(ctx context.Context, in *ReferenceDataRequest, opts ...client.CallOption) (*ReferenceDataResponse, error)
}
type evchargersService struct {
c client.Client
name string
}
func NewEvchargersService(name string, c client.Client) EvchargersService {
return &evchargersService{
c: c,
name: name,
}
}
func (c *evchargersService) Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) {
req := c.c.NewRequest(c.name, "Evchargers.Search", in)
out := new(SearchResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *evchargersService) ReferenceData(ctx context.Context, in *ReferenceDataRequest, opts ...client.CallOption) (*ReferenceDataResponse, error) {
req := c.c.NewRequest(c.name, "Evchargers.ReferenceData", in)
out := new(ReferenceDataResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Evchargers service
type EvchargersHandler interface {
Search(context.Context, *SearchRequest, *SearchResponse) error
ReferenceData(context.Context, *ReferenceDataRequest, *ReferenceDataResponse) error
}
func RegisterEvchargersHandler(s server.Server, hdlr EvchargersHandler, opts ...server.HandlerOption) error {
type evchargers interface {
Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error
ReferenceData(ctx context.Context, in *ReferenceDataRequest, out *ReferenceDataResponse) error
}
type Evchargers struct {
evchargers
}
h := &evchargersHandler{hdlr}
return s.Handle(s.NewHandler(&Evchargers{h}, opts...))
}
type evchargersHandler struct {
EvchargersHandler
}
func (h *evchargersHandler) Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error {
return h.EvchargersHandler.Search(ctx, in, out)
}
func (h *evchargersHandler) ReferenceData(ctx context.Context, in *ReferenceDataRequest, out *ReferenceDataResponse) error {
return h.EvchargersHandler.ReferenceData(ctx, in, out)
}

View File

@@ -0,0 +1,228 @@
syntax = "proto3";
package evchargers;
option go_package = "./proto;evchargers";
service Evchargers {
rpc Search(SearchRequest) returns (SearchResponse) {}
rpc ReferenceData(ReferenceDataRequest) returns (ReferenceDataResponse) {}
}
// Search by giving a coordinate and a max distance, or bounding box and optional filters
message SearchRequest {
// Coordinates from which to begin search
Coordinates location = 1;
// Search distance from point in metres, defaults to 5000m
int64 distance = 2;
// Bounding box to search within (top left and bottom right coordinates)
BoundingBox box = 3;
// Maximum number of results to return, defaults to 100
int64 max_results = 4;
// Country ID
string country_id = 5;
// IDs of the the EV charger operator
repeated string operators = 6;
// IDs of the connection type
repeated string connection_types = 7;
// Supported charging levels
repeated string levels = 8;
// Minimum power in KW. Note: data not available for many chargers
int64 min_power = 9;
// Usage of the charge point (is it public, membership required, etc)
repeated string usage_types = 11;
// TODO https://openchargemap.org/site/develop/api#POI
// verbose / compact to only return IDs for ref data
// polygon
// polyline
}
message Coordinates {
float latitude = 1;
float longitude = 2;
}
// Box to search (top left and bottom right coordinates)
message BoundingBox {
Coordinates bottom_left = 1;
Coordinates top_right = 2;
}
message SearchResponse {
repeated Poi pois = 1;
}
message Poi {
// The ID of the charger
string id = 1;
// The ID of the data provider
string data_provider_id = 2;
// The ID of the operator of the charger
string operator_id = 3;
// The type of usage for this charger point (is it public, membership required, etc)
string usage_type_id = 4;
// The address
Address address = 5;
// The connections available at this charge point
repeated Connection connections = 6;
// The number of charging points
int64 num_points = 7;
// The cost of charging
string cost = 8;
// The operator
Operator operator = 10;
// The type of usage
UsageType usage_type = 11;
}
message Address {
Coordinates location = 1;
string title = 2;
string address_line_1 = 3;
string address_line_2 = 4;
string town = 5;
string state_or_province = 6;
// Any comments about how to access the charger
string access_comments = 7;
string postcode = 8;
string country_id = 9;
Country country = 10;
}
message Connection {
// The ID of the connection type
string connection_type_id = 1;
string reference = 2;
// The level of charging power available
string level = 4;
// The amps offered
float amps = 5;
// The voltage offered
float voltage = 6;
// The power in KW
float power = 7;
// The current
string current = 8;
ConnectionType connection_type = 9;
}
// Retrieve reference data as used by this API
message ReferenceDataRequest {}
message ReferenceDataResponse {
// The types of charger
repeated ChargerType charger_types = 1;
// The types of connection
repeated ConnectionType connection_types = 2;
// The types of current
repeated CurrentType current_types = 3;
// The countries
repeated Country countries = 4;
// The providers of the charger data
repeated DataProvider data_providers = 5;
// The companies operating the chargers
repeated Operator operators = 6;
// The status of the charger
repeated StatusType status_types = 7;
// The status of a submission
repeated SubmissionStatusType submission_status_types = 8;
// The different types of usage
repeated UsageType usage_types = 9;
// The types of user comment
repeated UserCommentType user_comment_types = 10;
// The types of checkin status
repeated CheckinStatusType checkin_status_types = 11;
}
message ChargerType {
string id = 1;
string title = 2;
string comments = 3;
// Is this 40KW+
bool is_fast_charge_capable = 4;
}
message ConnectionType {
string id = 1;
string title = 2;
string formal_name = 3;
bool is_discontinued = 4;
bool is_obsolete = 5;
}
message CurrentType {
string id = 1;
string title = 2;
string description = 3;
}
message Country {
string id = 1;
string title = 2;
string iso_code = 3;
string continent_code = 4;
}
message DataProvider {
string id = 1;
string title = 2;
string website = 3;
string comments = 4;
DataProviderStatusType data_provider_status_type = 5;
// How is this data licensed
string license = 6;
}
message DataProviderStatusType {
string id = 1;
string title = 2;
bool is_provider_enabled = 3;
}
message Operator {
string id = 1;
string title = 2;
string website = 3;
string comments = 4;
// Is this operator a private individual vs a company
bool is_private_individual = 5;
string contact_email = 6;
string phone_primary = 7;
string phone_secondary = 8;
string fault_report_email = 9;
}
message StatusType {
string id = 1;
string title = 2;
bool is_operational = 3;
}
message SubmissionStatusType {
string id = 1;
string title = 2;
bool is_live = 3;
}
message UsageType {
string id = 1;
string title = 2;
bool is_pay_at_location = 3;
bool is_membership_required = 4;
bool is_access_key_required = 5;
}
message UserCommentType {
string id = 1;
string title = 2;
}
message CheckinStatusType {
string id = 1;
string title = 2;
bool is_positive = 3;
bool is_automated = 4;
}

View File

@@ -0,0 +1,5 @@
{
"name": "evchargers",
"icon": "🔋",
"category": "travel"
}