README
soda-angular
Socrata SODA client for Angular
Installation
npm install soda-angular --save
Also install these dev dependencies:
npm install geojson @types/geojson --save-dev
Usage
- Add the
SodaClientModule
to your module imports:
import { SodaClientModule } from 'soda-angular';
...
@NgModule({
...
imports: [
...
SodaClientModule.forRoot()
]
})
export class AppModule { }
- Create models for your dataset(s), decoated with a
@SodaDataset
that provides the dataset id:
import { FloatingTimestamp, Location } from 'soda-angular';
import { SodaDataset } from 'soda-angular';
@SodaDataset('8b78-2kux')
export class DevelopmentPermit {
city_file_number: string;
permit_type: string;
permit_class: string;
permit_date: FloatingTimestamp;
status: string;
description_of_development: string;
address: string;
legal_description: string;
neighbourhood_id: number;
neighbourhood: string;
zoning: string;
location: Location;
}
@SodaDataset('rwuh-apwg')
export class BuildingPermit {
row_id: string;
permit_number: string;
permit_date: FloatingTimestamp;
address: string;
legal_description: string;
neighbourhood: string;
job_description: string;
building_type: string;
construction_value: number;
zoning: string;
location: Location;
}
@SodaDataset('kk4c-7pcv')
export class LegalParcel {
id: number;
latitude: number;
longitude: number;
area: number;
geometry: MultiPolygon;
}
- Extend
SodaContext
with your own service context. Provide the URL to the Socrata service of your choice via the@SodaHost
decorator, and create yourSodaResource
objects with a dataset models:
import { Injectable } from '@angular/core';
import { SodaClient, SodaContext, SodaHost, SodaResource } from 'soda-angular';
@Injectable({
providedIn: 'root',
})
@SodaHost('https://data.edmonton.ca/')
export class OdpContext extends SodaContext {
public readonly developmentPermits: SodaResource<DevelopmentPermit>;
public readonly buildingPermits: SodaResource<BuildingPermit>;
constructor(sodaClient: SodaClient) {
super(sodaClient);
this.developmentPermits = new SodaResource(DevelopmentPermit, this);
this.buildingPermits = new SodaResource(BuildingPermit, this);
}
}
- Inject your Context into your component, and query against it using fluent querying:
import { Component, OnInit } from '@angular/core';
import { FloatingTimestamp, Location } from 'soda-angular';
@Component({
selector: 'permits',
templateUrl: './permits.component.html',
styleUrls: ['./permits.component.css']
})
export class PermitsComponent implements OnInit {
public DevelopmentPermits: DevelopmentPermit[];
public BuildingPermits: BuildingPermit[];
constructor(private context: OdpContext) { }
ngOnInit() {
this.context.developmentPermits
.where(p => p.permit_type)
.equals('Major Development Permit')
.and(p => p.permit_date)
.greaterThan(new FloatingTimestamp('04/23/2020 GMT'))
.and(p => p.zoning)
.not().equals('RF1')
.orderBy(p => neighbourhood)
.observable()
.subscribe(permits => this.DevelopmentPermits = permits);
this.context.buildingPermits
.where(p => p.permit_date)
.greaterThan(new FloatingTimestamp('04/23/2020 GMT'))
.and(p => p.neighbourhood)
.equals('DOWNTOWN')
.or(p => p.neighbourhood)
.equals('OLIVER')
.observable()
.subscribe(permits => this.BuildingPermits = permits);
}
}
Location type queries:
this.context.developmentPermits
.location(p => p.location)
.withinCircle(new Location(53.540959, -113.493819), 2000));
this.context.buildingPermits
.location(p => p.location)
.withinBox(
new Location(46.883198, -96.798216),
new Location(46.873169, -96.785139)
));
Geometry type queries:
import { MultiPolygon, Point } from 'geojson';
import { GeoJSONUtils } from 'soda-angular';
this.context.legalParcels
.geomery(p => p.geometry)
.intersects(GeoJSONUtils.point(-71.099290, -31.518292));
this.context.legalParcels
.geomery(p => p.geometry)
.intersects(GeoJSONUtils.polygon(
[
-113.599831726514,
53.458273089013
],
[
-113.600049996812,
53.45827360864
],
[
-113.600052949158,
53.457932503403
],
[
-113.599845224387,
53.457931995732
],
[
-113.599834691275,
53.457931970341
],
[
-113.599831726514,
53.458273089013
]
));
this.context.legalParcels
.geomery(p => p.latlon)
.withinPolygon(GeoJSONUtils.multipolygon(
[
[
-113.599831726514,
53.458273089013
],
[
-113.600049996812,
53.45827360864
],
[
-113.600052949158,
53.457932503403
],
[
-113.599845224387,
53.457931995732
],
[
-113.599834691275,
53.457931970341
],
[
-113.599831726514,
53.458273089013
]
]
));
Query builders
You can also use query builders for more control (including OR queries):
const builder = new SoqlQueryBuilder();
.filter(
new WhereFilter(
new Column('permit_type'),
Comparitor.Equals,
new WhereValue('Major Development Permit'),
),
new WhereOperator(Operator.And),
new WhereGroup(
new WhereFilter(
new Column('permit_value'),
Comparitor.GreaterThan,
new WhereValue(2000000),
),
new WhereOperator(Operator.Or),
new WhereGroup(
new WhereFilter(
new Column('permit_class'),
Comparitor.Equals,
new WhereValue('Class B'),
)
)
)
)
.offset(20)
.limit(20)
.orderBy(new Column('neighbourhood'));
this.context.developmentPermits
.get(builder.getQuery())
.subscribe(permits => this.Permits = permits);
Notes
- This is a work in progress, watch this repository for updates.
- Heavily inspired by Entity Framework.
- Filter grouping coming soon.
- Support for case-insensitive text matches is coming.
- Support for select-based functions are coming.
Additional Reading
- Socrata Developers
- yeg-dev-dashboard (Example project)
License
See LICENSE.