lizard-javascript-api
v0.2.2
Published
Library to interact with lizard assets, rasters and timeseries.
Downloads
4
Readme
Lizard JavaScript API
The goto library library for JavaScript clients to query and store Lizard web API data in a standardized and explicit way.
In ES2015:
import Lizard, {actions} from 'Lizard';
const lizard = Lizard();
In node:
var Lizard = require('Lizard').default();
var actions = require('Lizard').actions;
In ES5 load lib/Lizard.min.js
with a script tag and:
var Lizard = window.Lizard.default();
var actions = window.Lizard.actions;
Everywhere:
lizard.subscribe(function () { console.log(lizard.getState()) });
lizard.dispatch(actions.getAsset('pumpstation', 6853));
At any time lizard.getState()
returns a new object:
assets: {},
eventseries: {},
rasters: {},
timeseries: {},
intersections: {}
Assets, eventseries, rasters and timeseries contain metadata and intersections contain eventseries, timeseries and raster data for time and space intersections.
Prior art
- Google Maps JS API is a developer library for interfacing with Google Maps from Javascript.
- carto.js facilitates interaction with CartoDB.
- mapbox.js is a Mapbox specific wrapper around Leaflet.
- ArcGIS JS
- Boundless SDK
##Usage:
Lizard-API is a Redux store and as such follows the documentation of Redux. To use lizard-API you do not need any prior knowledge of Redux, though if you are developing a complex application or are already using redux: make sure to read the advanced section on redux and know how to combine your state with lizard.
Import Lizard by following the example above:
import Lizard, {actions} from 'Lizard-API';
const lizard = Lizard();
Now you have a Lizard store
and functions in actions
to create actions which can be dispatched to the store.
Let's add a pumpstation
const addPumpstation = actions.getAsset('pumpstation', 6853);
Where the first argument is the name of the Lizard entity you want to add and
the second is the id of the entity, corresponding to <lizard>/api/v2/pumpstations/6853
.
To perform this action:
const whenLizardPumpstationAdded = lizard.dispatch(addPumpstation);
The action addPumpstation
adds the available data to the store synchronously:
lizard.getState().assets
Returns:
{
pumpstation$6853: {entity: 'pumpstation', id: 6853}
}
It fetches the pumpstation data from the lizard server and returns a promise. To be informed when the request finishes, you can subscribe to changes to the store with a callback:
const lizardCallback => (state) { 'your entry point to perform your app logic' };
const unsubscribe = lizard.subscribe(lizardCallback)
Or by binding to the promise returned by the dispatch of asynchronous actions:
const showPumpstation => (pumpstationResponse) { 'your entry point to show the user a pumpstation' };
const apologizeToUser => (error) { 'No pumpstation for you my friend' };
whenLizardPumpstationAdded.then(showPumpstation, apologizeToUser);
Note: When you subscribe to lizard, you subscribe to all changes to the store. So when calling addPumpstation
your callback will be called at least twice. To stop listening to lizard changes, call the returned function unsubscribe()
.
Timeseries
In lizard, timeseries are connected to assets. For instance, pumpstation 6853 might have two connected timeseries, containing the measured water level on the polder and canal side of the station.
Dispatching the action addPumpstation
does three things: it adds an entry in assets
, it fills that entry with the asset definition from the server and it creates an entry in timeseries for every connected timeseries. So
whenLizardPumpstationAdded.then(() => lizard.getState());
Returns:
{
assets: {
pumpstation$6853: {
entity: 'pumpstation',
id: 6853
code: 'KGM-A-368',
name: 'De Waakzaamheid',
geometry: {
type: 'Point',
coordinates: [
4.895586999360215,
52.783234489793195,
0.0
]
},
},
...
}},
eventseries: {},
rasters: {},
timeseries: {
'e0e59d70-8cc8-45f0-9748-b6b627991e3c': {
parameter: 'Water level',
unit: 'm',
asset: { entity: 'pumpstation', id: 6853 },
location: 'Polder side',
...
},
'2cfd26ea-d126-4775-aa3c-e8df3efb3890': {
parameter: 'Water level',
unit: 'm',
asset: { entity: 'pumpstation', id: 6853 },
location: 'Canal side',
...
}
}
}
Intersections
When you want to show a chart of the polder side water level your app needs to dispatch an addIntersection
action. An intersection with a timeseries should contain the timeseries id
and optionally a time interval you are interested in plus any additional parameters you want to include in the request:
const whenIntersectionIsAdded = lizard.dispatch(actions.addIntersection('timeseries', {
id: 'e0e59d70-8cc8-45f0-9748-b6b627991e3c',
params: {min_points: 300}
}))
By default intersections are not active and have no specified start and end. Intersections can be added with active: true
and a spaceTime
object or can be changed by dispatching an action:
whenIntersectionIsAdded = lizard.dispatch(actions.setIntersectionSpaceTime('0', {
start: 1356998400000,
end: 1482035400000
}));
whenIntersectionIsAdded = lizard.dispatch(actions.toggleIntersection('0'));
Where 0
is the intersection's key in lizard.getState().intersections
. If an intersection is active or toggled to active, both actions will fetch timeseries data and return a promise:
whenIntersectionIsAdded.then(() => lizard.getState());
Returns:
...
intersections: {
0: {
type: 'timeseries';
id: 'e0e59d70-8cc8-45f0-9748-b6b627991e3c',
spaceTime: {
start: 1356998400000,
end: 1482035400000
},
active: true,
params: {min_points: 300},
events: [
{timestamp: 1356998403600, value: 7},
{timestamp: 1356998407200, value: 4},
...
]
}
}
Note: the Lizard store contains timeseries metadata in timeseries and timeseries data in intersections. You do not need to have a timeseries metadata object for an intersection.
To read about min_points
and other parameters you may include to format timeseries data, go to the lizard REST api documentation.
Advanced: using redux
If your app is using Redux or you want to use redux, need to proceed with some caution not to violate Redux principle of having only one source of truth in you app and thus only store. Therefore, when ininitializing Lizard, pass your app's reducers as the second argument:
const myAppEntities = {
entityName: entityReducer
}
const store = Lizard({}, myAppEntities);
To initialize the store with preloaded data, for instance to perform server side hydration, use the first argument:
const store = Lizard({
assets: {
pupstation$123: {
entity: 'pumpstation',
id: 123,
name: 'You got to stay hydrated'
}
}
});
A real life node example
Lizard Javascript API uses window.location for its requests. In production this means you either need an instance of the Lizard web API or proxy requests from your server. In development running npm start
starts a web server which proxies all requests to http://localhost:8000
. If window
is unavailable it defaults http://demo.lizard.net
. In the directory of Lizard JavaScript API try:
Build a compiled version:
node_modules/webpack/bin/webpack.js
node
Load public data:
var lizard = require('./lib/Lizard').default();
var actions = require('./lib/Lizard').actions;
lizard.dispatch(actions.getAsset('measuringstation', 202094));
lizard.dispatch(actions.addIntersection('timeseries', { typeId: '8f3b0455-d967-40b3-a739-a0a96225d293', spaceTime:{ start: 946252800000, end: 1467504000000 }, params: { min_points: 10 } }));
lizard.dispatch(actions.toggleIntersection('0'));
lizard.subscribe(function () { console.log(lizard.getState()) });
A note on map tiles
Lizard JavaScript API is meant to query and store Lizard resources in JSON. Lizard also makes assets and rasters available as tiled map services through the TMS and WMS protocol. To use these resources in a browser you should use map libraries such as Leaflet or Openlayers.
TODO: this route is very implicit, how are outsiders supposed to find out how this works?
Development
The generated project includes a development server on port 8080
which will reload a rebuild version of the library into the browser whenever you change source code. To start the server and run the test with watch function, run:
$ npm start
To build for production, this command will run the tests and output optimized production code in lib/Lizard.js:
$ npm build
To run the tests once:
$ npm test
Note: npm start
creates a development version of the library in memory, the version on disk in lib/
is not automatically updated.
TODO: the tests run continuously but do not have access to webpacks build version of the library in memory. Therefore the test import src/
. This is bad practice and something we should fix.
Developer checklist:
- [ ] Fork/Clone this repo.
- [ ] Add constants/actions/reducers.
- [ ] Document new functionality.
- [ ] Test your code using Chai (preferably include a test with the build library).
- [ ] Lint your code with eslint rules.
- [ ] Create an atomic commit with standard-version commit guidelines. Only use
feat(new-feat)
when making the library compliant with a new API version. So the major version of this library always matches the web api versions. - [ ] Submit a pull request.
TODO:
As of to date it is only a quarter done.
- [ ] Test all actions. Currently some action dispatchers and some reducers are tested. We should write more integration-like tests which read like documentation and dispatch every public action possible.
- [ ] Remove all timeseries of asset when removing asset.
- [ ] Remove all intersections when raster/eventeries/timeseries are removed.
- [ ] Include eventseries.
- [ ] Check user input. Currently an action payload is passed all the way through without ever checking what is inside. Throw errors.
- [ ] Throw errors and call error callbacks when request goes bad or response is malformed.
- [ ] Cancel requests when no longer relevant.
- [ ] Store server responses in a consistent manner.
- [ ] Include buck trap for releases.
- [ ] Use documentation generator.
- [ ] Use library in apps.
- [ ] Create demo page.
- [ ] Create documentation for all actions.
- [ ] Check for consistent isLoading flags, and document intended use.
- [ ] Include caching.