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

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"`
}