Commit c088bae2 authored by Rune Åvar Ødegård's avatar Rune Åvar Ødegård
Browse files

Initial commit

parents
# pollenviz
The purpose of the application is store and visualise sensor data.
## PLEASE NOTE
This branch (master) is under development and will frequently change.
Please use the v1.0 tag to install pollenviz.
## Features
* Simple data import with csv files via gui or api
* GUI for editing data
* View and compare data
## Requirements
The following is required to install and run the application:
* Postgres 9.5+ (https://www.postgresql.org/download/)
* Postgis extension (https://postgis.net/install/)
* Python 3 (https://www.python.org/downloads/)
* Yarn/NPM
* WSGI complient server
* See requirements.txt for external python libraries used
## Configuration
Add a file in the web folder called config.ini.
Look at config.example.ini to see what needs to be inside.
## Database
Setup postgres database with the postgis extension.
Import the schema.sql file from the db_scripts folder.
## Running the app
Install all required python libraries with:
```powershell
pip install -r requirements.txt
```
Navigate to the web/client folder and build the gui with the command:
```powershell
yarn install
yarn build
```
Run the python server with the the following command:
**Linux**
```powershell
export FLASK_APP=web
flask run
```
**Windows**
```powershell
$env:FLASK_APP = "web"
flask run
```
See https://flask.palletsprojects.com/en/1.1.x/quickstart/ for more information.
## Development
Make sure you have all javascript packages installed by running `yarn` in the web/client folder.
Run `yarn build` once so the python server has the required files.
Start the web server as described in "running the app"
Start the client side server by running the command `yarn serve`
## Linux web server
On the webserver we have to start with a configuration for apache2. Write a custom configuration file, /etc/apache2/sites-available/pollenviz.conf.
The ServerName settings in pollenviz.conf has to be registered as a CNAME, pointing to the A-record for the webserver, in the DNS-servers.
Make ssl-certs for https://someurl.com, and put the key and crt file into /etc/apache2/ssl.
Make a DocumentRoot directory under /var/www/html, call it pollenviz. Give it sufficient permissions for apache2 to be able to read it.
Copy all of the files in the web folder to there.
Make a pollenviz.wsgi file, to start the application. Here we source the virtual environment and starts the application.
Next is to make a virtual environment for python, and then installing all the modules from requirement.txt from that. psycopg2 is dependent on libpq-dev and python-dev.
## IIS
Please follow this article to install the application on IIS:
http://netdot.co/2015/03/09/flask-on-iis/
Make sure the Python folder has IIS_IUSRS rights.
Install all dependencies from requirements.txt globally or with virtualenv.
To do this globally run the following line in the python script folder:
```pip install -r requirements.txt```
It is highly recommended that you set up your server with **https**
## Importing data
Importing data is done by uploading sensors and observations as csv files. See csv_examples.
Use [Curl](https://curl.haxx.se/), [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop) or similar tools for this.
Set Content-Type to "multipart/form-data" and use the form name "csv".
Example uploading networks with curl
```
curl -X POST -u usr:psw --form "csv=@path/to/sensors.csv" https://someurl.com/imports/sensors
```
If you upload a content with id's that exists, it will be updated.
There are dependencies between the csv files, so the import order is important.
Start with sensors and observations
### Security
This application uses basic authentication.
The default user:password combination is admin:admin
It is recommended to change the default password immediately.
You can do this with the user management module on pollenviz web site.
from web import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=False)
\ No newline at end of file
/*
Source Database : pollenviz
Source Schema : public
Date: 2019-12-16 22:47:00
*/
CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public;
COMMENT ON EXTENSION postgis IS 'PostGIS geometry, geography, and raster spatial types and functions';
CREATE TABLE public.parameters (
id integer NOT NULL,
label character varying(100) NOT NULL,
notation character varying(100) NOT NULL
);
ALTER TABLE public.parameters OWNER TO postgres;
CREATE TABLE public.units (
id integer NOT NULL,
label character varying(100) NOT NULL,
notation character varying(100) NOT NULL
);
ALTER TABLE public.units OWNER TO postgres;
CREATE TABLE public.timesteps (
id integer NOT NULL,
label character varying(100) NOT NULL,
notation character varying(100) NOT NULL
);
ALTER TABLE public.timesteps OWNER TO postgres;
CREATE TABLE public.sensors
(
id integer NOT NULL,
name character varying(100) NOT NULL,
parameter integer NOT NULL,
unit integer NOT NULL,
timestep integer NOT NULL,
from_time timestamp without time zone,
to_time timestamp without time zone,
geom geometry NOT NULL,
comment character varying(500),
CONSTRAINT sensors_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.sensors OWNER TO postgres;
CREATE TABLE public.observations
(
id bigint NOT NULL DEFAULT nextval('observations_id_seq'::regclass),
sensor_id integer NOT NULL,
value numeric(255,5) NOT NULL,
from_time timestamp without time zone NOT NULL,
to_time timestamp without time zone NOT NULL
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.observations OWNER to postgres;
ALTER TABLE ONLY public.observations ADD CONSTRAINT observations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.observations
ADD CONSTRAINT observations_sensors_id_fkey FOREIGN KEY (sensor_id) REFERENCES public.sensors(id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE public.observations
ADD CONSTRAINT sensor_id_from_to UNIQUE (sensor_id, from_time, to_time);
CREATE SEQUENCE public.observations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.observations_id_seq OWNER TO postgres;
ALTER SEQUENCE public.observations_id_seq OWNED BY public.observations.id;
CREATE TABLE public.users (
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
created timestamp without time zone,
createdby character varying(255)
);
ALTER TABLE public.users OWNER TO postgres;
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;
CREATE FUNCTION public.pollenviz_update_time()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
DECLARE
fromtime timestamp without time zone;
totime timestamp without time zone;
BEGIN
SELECT from_time, to_time
INTO fromtime, totime
FROM sensors
WHERE id = NEW.sensor_id;
-- UPDATE FROMTIME IN THE SENSORS TABLE
IF fromtime IS NULL OR NEW.from_time < fromtime THEN
UPDATE sensors SET from_time = NEW.from_time WHERE id = NEW.sensor_id;
END IF;
-- UPDATE TOTIME IN THE SENSORS TABLE
IF totime IS NULL OR NEW.to_time > totime THEN
UPDATE sensors SET to_time = NEW.to_time WHERE id = NEW.sensor_id;
END IF;
RETURN NEW;
END;
$BODY$;
ALTER FUNCTION public.pollenviz_update_time()
OWNER TO postgres;
CREATE TRIGGER pollenviz_update_time_trigger
AFTER INSERT
ON public.observations
FOR EACH ROW
EXECUTE PROCEDURE public.pollenviz_update_time();
INSERT INTO public.users VALUES ('admin', 'pbkdf2:sha256:150000$JAxTlbED$0fb0aa01ce950de3204185af3d4c61001d9b077895067b050b2ecd78d6835cd8', NULL, NULL);
INSERT INTO public.parameters VALUES (1, 'Temperature', 'Temp');
INSERT INTO public.parameters VALUES (2, 'Relative Humidity', 'RH');
INSERT INTO public.units VALUES (3, 'Percentage', '%');
INSERT INTO public.units VALUES (4, 'birch', 'cnt/m3');
INSERT INTO public.units VALUES (5, 'hazel', 'cnt/m3');
INSERT INTO public.units VALUES (6, 'ash', 'cnt/m3');
INSERT INTO public.timesteps VALUES (1, 'Second', 's');
INSERT INTO public.timesteps VALUES (2, 'Minute', 'm');
INSERT INTO public.timesteps VALUES (3, 'Hour', 'h');
INSERT INTO public.timesteps VALUES (4, 'Variable', 'variable');
INSERT INTO public.timesteps VALUES (5, 'Instantaneous', 'instantaneous');
../cardinal/bin/cdl.js
\ No newline at end of file
../esprima/bin/esparse.js
\ No newline at end of file
../esprima/bin/esvalidate.js
\ No newline at end of file
../@mapbox/geojson-rewind/geojson-rewind
\ No newline at end of file
../pbf/bin/pbf
\ No newline at end of file
../sharkdown/sharkdown
\ No newline at end of file
{
"systemParams": "darwin-x64-64",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}
\ No newline at end of file
<a name="0.2.2"></a>
## [0.2.2](https://github.com/mapbox/geojson-area/compare/v0.2.1...v0.2.2) (2016-12-19)
### Bug Fixes
* **algorithm:** Avoid accidental global i, fixes use strict ([1646944](https://github.com/mapbox/geojson-area/commit/1646944))
Copyright 2005-2013 OpenLayers Contributors. All rights reserved. See
authors.txt for full list.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of OpenLayers Contributors.
[![Build Status](https://travis-ci.org/mapbox/geojson-area.png)](https://travis-ci.org/mapbox/geojson-area)
# geojson-area
Calculate the area inside of any [GeoJSON](http://geojson.org/) geometry.
## usage
npm install @mapbox/geojson-area
## example
```js
var geojsonArea = require('@mapbox/geojson-area');
var area = geojsonArea.geometry(obj);
```
## api
### `geojsonArea.geometry(obj)`
Given a Geometry object, return contained
area as square meters. Invalid input will return `null`.
Adapted from [OpenLayers](http://openlayers.org/)
var wgs84 = require('wgs84');
module.exports.geometry = geometry;
module.exports.ring = ringArea;
function geometry(_) {
var area = 0, i;
switch (_.type) {
case 'Polygon':
return polygonArea(_.coordinates);
case 'MultiPolygon':
for (i = 0; i < _.coordinates.length; i++) {
area += polygonArea(_.coordinates[i]);
}
return area;
case 'Point':
case 'MultiPoint':
case 'LineString':
case 'MultiLineString':
return 0;
case 'GeometryCollection':
for (i = 0; i < _.geometries.length; i++) {
area += geometry(_.geometries[i]);
}
return area;
}
}
function polygonArea(coords) {
var area = 0;
if (coords && coords.length > 0) {
area += Math.abs(ringArea(coords[0]));
for (var i = 1; i < coords.length; i++) {
area -= Math.abs(ringArea(coords[i]));
}
}
return area;
}
/**
* Calculate the approximate area of the polygon were it projected onto
* the earth. Note that this area will be positive if ring is oriented
* clockwise, otherwise it will be negative.
*
* Reference:
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
* Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
*
* Returns:
* {float} The approximate signed geodesic area of the polygon in square
* meters.
*/
function ringArea(coords) {
var p1, p2, p3, lowerIndex, middleIndex, upperIndex, i,
area = 0,
coordsLength = coords.length;
if (coordsLength > 2) {
for (i = 0; i < coordsLength; i++) {
if (i === coordsLength - 2) {// i = N-2
lowerIndex = coordsLength - 2;
middleIndex = coordsLength -1;
upperIndex = 0;
} else if (i === coordsLength - 1) {// i = N-1
lowerIndex = coordsLength - 1;
middleIndex = 0;
upperIndex = 1;
} else { // i = 0 to N-3
lowerIndex = i;
middleIndex = i+1;
upperIndex = i+2;
}
p1 = coords[lowerIndex];
p2 = coords[middleIndex];
p3 = coords[upperIndex];
area += ( rad(p3[0]) - rad(p1[0]) ) * Math.sin( rad(p2[1]));
}
area = area * wgs84.RADIUS * wgs84.RADIUS / 2;
}
return area;
}
function rad(_) {
return _ * Math.PI / 180;
}
\ No newline at end of file
{
"_from": "@mapbox/geojson-area@0.2.2",
"_id": "@mapbox/geojson-area@0.2.2",
"_inBundle": false,
"_integrity": "sha1-GNeBSqNr8j+7zDefjiaiKSfevxA=",
"_location": "/@mapbox/geojson-area",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "@mapbox/geojson-area@0.2.2",
"name": "@mapbox/geojson-area",
"escapedName": "@mapbox%2fgeojson-area",
"scope": "@mapbox",
"rawSpec": "0.2.2",
"saveSpec": null,
"fetchSpec": "0.2.2"
},
"_requiredBy": [
"/@mapbox/geojson-rewind"
],
"_resolved": "https://registry.npmjs.org/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz",
"_shasum": "18d7814aa36bf23fbbcc379f8e26a22927debf10",
"_spec": "@mapbox/geojson-area@0.2.2",
"_where": "/Users/runeodegard/code/firefly/node_modules/@mapbox/geojson-rewind",
"author": {
"name": "Tom MacWright"
},
"bugs": {
"url": "https://github.com/mapbox/geojson-area/issues"
},
"bundleDependencies": false,
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"dependencies": {
"wgs84": "0.0.0"
},
"deprecated": false,
"description": "calculate the physical area of a geojson geometry",
"devDependencies": {
"cz-conventional-changelog": "^1.2.0",
"tape": "^3.0.3"
},
"directories": {
"test": "test"
},
"homepage": "https://github.com/mapbox/geojson-area#readme",
"keywords": [
"geojson",
"area",
"geodesy"
],
"license": "BSD-2-Clause",
"main": "index.js",
"name": "@mapbox/geojson-area",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/mapbox/geojson-area.git"
},
"scripts": {
"test": "tape test/basic.js"
},
"version": "0.2.2"
}
{
"type": "Polygon",
"coordinates": [[
[-180, -90],
[-180, 90],
[180, 90],
[180, -90],
[-180, -90]
]
]
}
var gjArea = require('../'),
test = require('tape'),
ill = require('./illinois.json'),
all = require('./all.json');
test('geojson area', function(t) {
t.test('computes the area of illinois', function(t) {
t.equal(gjArea.geometry(ill), 145978332359.36746);
t.end();
});
// http://www.wolframalpha.com/input/?i=surface+area+of+earth
t.test('computes the area of the world', function(t) {
t.equal(gjArea.geometry(all), 511207893395811.06);
t.end();
});
t.test('point has zero area', function(t) {
t.equal(gjArea.geometry({ type: 'Point', coordinates: [0,0] }), 0);
t.end();
});
t.test('linestring has zero area', function(t) {
t.equal(gjArea.geometry({ type: 'LineString', coordinates: [[0,0],[1,1]] }), 0);
t.end();
});
t.test('geometrycollection is the sum', function(t) {
t.equal(gjArea.geometry({ type: 'GeometryCollection', geometries: [all, ill] }), 511353871728170.44);
t.end();
});
t.end();
});
{
"type": "MultiPolygon",
"coordinates": [[
[
[
-88.071564,
37.51099
],
[
-88.087883,
37.476273
],
[
-88.311707,
37.442852
],
[
-88.359177,
37.409309
],
[
-88.419853,
37.420292
],
[
-88.467644,
37.400757
],
[
-88.511322,