Compare commits
30 commits
Author | SHA1 | Date | |
---|---|---|---|
8d5b74a773 | |||
ea4063917b | |||
1e6f858426 | |||
3aeb91101d | |||
f40692b483 | |||
6c923de5ac | |||
5eec2d3188 | |||
8a5cf8c1ee | |||
10fcf55b10 | |||
bf6ccf5094 | |||
522c609ef1 | |||
2b97fe8b10 | |||
99a42e3b7a | |||
1ac9d096ff | |||
fdd2862d5f | |||
15ade89904 | |||
5e12ac6b38 | |||
d9897c57eb | |||
cbdbf7497b | |||
7875895248 | |||
cb27186641 | |||
d8830d77aa | |||
41b5ea2ab1 | |||
b85516e397 | |||
93f4ce6145 | |||
9af7ddc4e2 | |||
985fbf7600 | |||
669c665a10 | |||
bb8c48d75b | |||
![]() |
070ccc9a19 |
57
.forgejo/workflows/images.yml
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
name: Build (and tag) Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.hamburg.ccc.de
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set image tags
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.ref_name }}" = "main" ] && [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
echo "tag=latest" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build web image
|
||||||
|
run: |
|
||||||
|
docker build -f ./Containerfile -t git.hamburg.ccc.de/ccchh/sunders/web:${{ steps.vars.outputs.tag }} .
|
||||||
|
working-directory: ./web
|
||||||
|
|
||||||
|
- name: Build data_handler image
|
||||||
|
run: |
|
||||||
|
docker build -f ./Containerfile -t git.hamburg.ccc.de/ccchh/sunders/data_handler:${{ steps.vars.outputs.tag }} .
|
||||||
|
working-directory: ./data_handler
|
||||||
|
|
||||||
|
- name: Push images to Container Registry
|
||||||
|
run: |
|
||||||
|
docker push git.hamburg.ccc.de/ccchh/sunders/web:${{ steps.vars.outputs.tag }}
|
||||||
|
docker push git.hamburg.ccc.de/ccchh/sunders/data_handler:${{ steps.vars.outputs.tag }}
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
|
||||||
|
- name: Update docker-compose.yml image tags
|
||||||
|
run: |
|
||||||
|
sed -i "s/:latest/:${{ steps.vars.outputs.tag }}/g" docker-compose.yml
|
||||||
|
|
||||||
|
- name: Start Docker Compose services
|
||||||
|
run: |
|
||||||
|
docker compose up -d --wait
|
||||||
|
docker compose down
|
1
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
||||||
change_file.osc*
|
change_file.osc*
|
||||||
config.php
|
|
||||||
lastState.txt
|
lastState.txt
|
||||||
lastStatisticsUpdate.txt
|
lastStatisticsUpdate.txt
|
||||||
log.*
|
log.*
|
||||||
|
|
121
README.md
|
@ -22,124 +22,35 @@ Color | Description
|
||||||
|
|
||||||
A running instance of this project can be visited at [https://sunders.uber.space/](https://sunders.uber.space/).
|
A running instance of this project can be visited at [https://sunders.uber.space/](https://sunders.uber.space/).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [Docker](https://www.docker.com)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
If you like to run Surveillance under Surveillance on your own LAMP or LNMP server follow these steps:
|
To run Surveillance using docker, decide between development/testing or production deployments:
|
||||||
|
|
||||||
1. Get the sources
|
### Development/testing
|
||||||
|
|
||||||
- Copy the content of **home/sunders/** to your home directory, e.g. to **~/sunders/**.
|
1. Clone this repository
|
||||||
|
|
||||||
- Copy the content of **www/sunders/** to your server's www directory, e.g. to **/var/www/sunders/**.
|
2. Run `docker compose up` and wait for services to start up
|
||||||
|
|
||||||
2. Set up the database
|
3. Visit `http://localhost:8080/` in a browser; you should see an interactive OpenStreetMap
|
||||||
|
|
||||||
- Change to the directory **~/sunders/init_cameras/db/**.
|
_tbd.: Populate camera data_
|
||||||
|
|
||||||
- Open the file **createDB.sql** and enter a password for the new database user **camera**.
|
### Production
|
||||||
|
|
||||||
- Create the database **camera** by executing the file **createDB.sql**.
|
1. Close this repository
|
||||||
|
|
||||||
`mysql -h localhost -u root --password=[mysql root password] < createDB.sql`
|
2. Hardern security by opening `docker-compose.yml` and modifying `MYSQL_PASSWORD` and `MYSQL_ROOT_PASSWORD`
|
||||||
|
|
||||||
3. Initialize the database
|
3. Run `docker compose up` and wait for services to start up
|
||||||
|
|
||||||
Decide whether you like to start with the surveillance entries of the first planet.osm file from September 12, 2012 or if you like to start with the latest planet.osm file or an extract of an individual country or region.
|
3. Visit `http://localhost:8080/` in a browser to open the web interface
|
||||||
|
|
||||||
**Start with the surveillance entries of the first planet.osm file from September 12, 2012**
|
_tbd.: Populate camera data_
|
||||||
|
|
||||||
Pros: You don't have to download the latest +50GB planet.osm file to create a sql import file.
|
|
||||||
|
|
||||||
Cons: It could take several days until your database is up-to-date and contains all surveillance entries that have been added between September 12, 2012 and today. Furthermore you start with a data record for the whole planet. Maybe you are only interested in the data of a certain country or region.
|
|
||||||
|
|
||||||
- Execute the file **initializeDB_planet_20120912.sql** for database user **camera**.
|
|
||||||
|
|
||||||
`mysql camera -h localhost -u camera --password=[camera user password] < initializeDB_planet_20120912.sql`
|
|
||||||
|
|
||||||
**Start with the latest planet.osm file or an extract of an individual country or state**
|
|
||||||
|
|
||||||
Pros: You start with the latest data records. Furthermore you can choose what country or region you like to map.
|
|
||||||
|
|
||||||
Cons: If you like to map the whole planet you have to download the latest +50GB planet.osm file. According to your internet connection this could take a while. At last you have to install the command line Openstreetmap data processor [Osmosis](https://wiki.openstreetmap.org/wiki/Osmosis) on your computer.
|
|
||||||
|
|
||||||
- Download the latest osm.bz2 file you like to extract the data from, e.g. from [planet.openstreetmap.org](https://planet.openstreetmap.org/) or from [download.geofabrik.de](http://download.geofabrik.de/). They also offer a MD5 sum to verify the downloaded file.
|
|
||||||
|
|
||||||
- Copy the just downloaded osm.bz2 file to the directory **~/sunders/init_cameras/**.
|
|
||||||
|
|
||||||
- Open the file **~/sunders/init_cameras/createInitialDataFiles.sh** and enter the name of the osm.bz2 file as **XML_FILE**.
|
|
||||||
|
|
||||||
`XML_FILE=[file name].osm.bz2`
|
|
||||||
|
|
||||||
- Execute **createInitialDataFiles.sh** to create the files **surveillance.osm** and **initializeDB.sql**.
|
|
||||||
|
|
||||||
- Move the new files **surveillance.osm** and **initializeDB.sql** to the directory **~/sunders/init_cameras/db/** and change to that directory.
|
|
||||||
|
|
||||||
- Execute the file **initializeDB.sql** for database user **camera**.
|
|
||||||
|
|
||||||
`mysql camera -h localhost -u camera --password=[camera user password] < initializeDB.sql`
|
|
||||||
|
|
||||||
4. Update the database
|
|
||||||
|
|
||||||
- Change to the directory **~/sunders/update_cameras/**.
|
|
||||||
|
|
||||||
- Rename the file **config.php.example** to **config.php**.
|
|
||||||
|
|
||||||
- Open the file **config.php** and enter the **MYSQL_PASSWD** of the database user **camera**. Furthermore enter the **REPLICATE_URL** that fits to your project, e.g. from [planet.openstreetmap.org](https://planet.openstreetmap.org/replication/) or from [download.openstreetmap.fr](http://download.openstreetmap.fr/replication/). Here are some examples:
|
|
||||||
|
|
||||||
`https://planet.openstreetmap.org/replication/minute/`
|
|
||||||
`http://download.openstreetmap.fr/replication/planet/minute/`
|
|
||||||
`http://download.openstreetmap.fr/replication/europe/minute/`
|
|
||||||
`http://download.openstreetmap.fr/replication/europe/netherlands/minute/`
|
|
||||||
|
|
||||||
- The update process is based on the sequence number comparison between the current **state.txt** file from the replication server, and the locally stored **lastState.txt**. So if you downloaded a osm.bz2 file, you should modify the **sequenceNumber** in the **lastState.txt** file accordingly.
|
|
||||||
|
|
||||||
- Execute the file **update_camera.sh** to import all surveillance entries that have been added between the creation of the osm.bz2 file and today.
|
|
||||||
|
|
||||||
5. Schedule automatic database updates
|
|
||||||
|
|
||||||
- Add this line to your crontab:
|
|
||||||
|
|
||||||
`* * * * * /home/[user]/sunders/update_cameras/update_camera.sh > /dev/null 2>&1`
|
|
||||||
|
|
||||||
- Go to the directory **~/sunders/update_cameras/logs** to check if your schedule works. After one minute there should be a new log file.
|
|
||||||
|
|
||||||
- Go back to your crontab to change the schedule to the values you prefer, e.g. to `23 * * * *` to run the update at every 23rd minute past every hour.
|
|
||||||
|
|
||||||
6. Configure the website
|
|
||||||
|
|
||||||
- Change to the directory **/var/www/sunders/**.
|
|
||||||
|
|
||||||
- Rename the file **config.php.example** to **config.php**.
|
|
||||||
|
|
||||||
- Open the file **config.php** and change the definitions of **DEFAULT_ZOOM**, **DEFAULT_LAT**, and **DEFAULT_LON** to set the initial focus of the map to the location you want. Furthermore you can set the **DEFAULT_LANGUAGE**.
|
|
||||||
|
|
||||||
7. Check the website
|
|
||||||
|
|
||||||
- Enter the URL of your Surveillance under Surveillance instance (e.g. https://myserver/sunders/) into your browser.
|
|
||||||
|
|
||||||
- You should see a map with camera icons now.
|
|
||||||
|
|
||||||
8. Optional: Add statistics table to your database
|
|
||||||
|
|
||||||
- Go to the directory **~/sunders/init_cameras/db/**.
|
|
||||||
|
|
||||||
- Execute the file **addStatistics.sql** for database user **camera**.
|
|
||||||
|
|
||||||
`mysql camera -h localhost -u camera --password=[camera user password] < addStatistics.sql`
|
|
||||||
|
|
||||||
- Open the file **~/sunders/update_cameras/config.php**. Change the value of **USE_STATISTICS** from **false** to **true** and enter the username of your GeoNames account at **WEBSERVICE_USER**.
|
|
||||||
|
|
||||||
`define('USE_STATISTICS', true);`
|
|
||||||
|
|
||||||
- Open the file **/var/www/sunders/config.php** and change the value of **USE_STATISTICS** from **false** to **true**.
|
|
||||||
|
|
||||||
`define('USE_STATISTICS', true);`
|
|
||||||
|
|
||||||
- Schedule automatic statistics updates by adding this new line to your crontab. Enter your preferred schedule values, e.g. `42 * * * *` to run the update at every 42nd minute past every hour.
|
|
||||||
|
|
||||||
`42 * * * * /home/[user]/sunders/update_cameras/update_statistics.sh > /dev/null 2>&1`
|
|
||||||
|
|
||||||
- Check the statistics by entering the URL of the visualization site (e.g. https://myserver/sunders/stats/) into your browser.
|
|
||||||
|
|
||||||
## Surveillance nodes
|
## Surveillance nodes
|
||||||
|
|
||||||
|
|
8
data_handler/Containerfile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
FROM ghcr.io/asohh/fedora-minimal-containers/php-cli:43
|
||||||
|
RUN microdnf install -y php-mysqli php-bcmath php-xml gunzip https://raw.githubusercontent.com/rpmsphere/x86_64/master/d/dcron-4.5-7.1.x86_64.rpm && microdnf clean all
|
||||||
|
COPY ./utils/* /opt/
|
||||||
|
COPY ./data_init/*.sql /opt/init/init.sql
|
||||||
|
|
||||||
|
ENTRYPOINT ["/opt/entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD ["/usr/sbin/dcrond", "-f" ]
|
117667
data_handler/data_init/initializeDB_planet_20120912.sql
Normal file
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
define('REPLICATE_URL', 'https://planet.openstreetmap.org/replication/minute');
|
define('REPLICATE_URL', 'https://planet.openstreetmap.org/replication/minute');
|
||||||
|
|
||||||
define('MYSQL_HOST', 'localhost');
|
define('MYSQL_HOST', getenv('MYSQL_HOST'));
|
||||||
define('MYSQL_DB', 'camera');
|
define('MYSQL_DB', getenv('MYSQL_DB'));
|
||||||
define('MYSQL_USER', 'camera');
|
define('MYSQL_USER', getenv('MYSQL_USER'));
|
||||||
define('MYSQL_PASSWD', 'xxxxxxxx');
|
define('MYSQL_PASSWORD', getenv('MYSQL_PASSWORD'));
|
||||||
|
|
||||||
define('USE_STATISTICS', false);
|
define('USE_STATISTICS', false);
|
||||||
define('WEBSERVICE_COUNTRY_URL', 'http://api.geonames.org/countryCode');
|
define('WEBSERVICE_COUNTRY_URL', 'http://api.geonames.org/countryCode');
|
29
data_handler/utils/entrypoint.sh
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
### Crontab settings
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
|
cat<<EOF | dcrontab -
|
||||||
|
# min hour day month weekday command
|
||||||
|
5 * * * * sh /opt/update_camera.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
### Run prescripts
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
|
php /opt/init_db.php
|
||||||
|
#TODO add db_migrations
|
||||||
|
echo "Prescripts done"
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
### Run cmd
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
|
# see: https://github.com/dubiousjim/dcron/issues/13
|
||||||
|
# ignore using `exec` for `dcron` to get another pid instead of `1`
|
||||||
|
# exec "$@"
|
||||||
|
"$@"
|
21
data_handler/utils/get_sync_state.php
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
include "config.php";
|
||||||
|
|
||||||
|
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
|
||||||
|
|
||||||
|
if($mysqli->connect_errno) {
|
||||||
|
echo "Error while connecting to DB : $mysqli->error \n" ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$mysqli->autocommit(FALSE);
|
||||||
|
|
||||||
|
$result = $mysqli->query("SELECT * FROM sync_state WHERE k = 'sequenceNumber'");
|
||||||
|
|
||||||
|
while($row = $result->fetch_assoc()) {
|
||||||
|
echo $row["v"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$mysqli->close();
|
||||||
|
|
||||||
|
?>
|
97
data_handler/utils/init_db.php
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
<?php
|
||||||
|
// Admin credentials (must have privileges to create DB/users)
|
||||||
|
$dbHost = getenv('MYSQL_HOST');
|
||||||
|
$dbAdmin = getenv('MYSQL_USER');
|
||||||
|
$dbPassword = getenv('MYSQL_PASSWORD');
|
||||||
|
$dbName = getenv('MYSQL_DB');
|
||||||
|
|
||||||
|
// Variables for new users
|
||||||
|
$cameraUser = getenv('CAMERA_USER');
|
||||||
|
$cameraPassword = getenv('CAMERA_USER_PASSWORD');
|
||||||
|
|
||||||
|
$camSelectUser = getenv('CAMERA_SELECT_USER');
|
||||||
|
$camSelectPassword = getenv('CAMERA_SELECT_USER_PASSWORD');
|
||||||
|
|
||||||
|
// Connect to MySQL
|
||||||
|
$conn = new mysqli($dbHost, $dbAdmin, $dbPassword);
|
||||||
|
|
||||||
|
// Check connection
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Connection failed: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!$conn->query("CREATE DATABASE IF NOT EXISTS $dbName")) {
|
||||||
|
echo "Error creating database: " . $conn->error . "\n";
|
||||||
|
}
|
||||||
|
if (!$conn->select_db($dbName)) {
|
||||||
|
die("Error selecting database: " . $conn->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create camera user
|
||||||
|
$conn->query("CREATE USER IF NOT EXISTS '$cameraUser'@'%' IDENTIFIED BY '$cameraPassword'");
|
||||||
|
$conn->query("GRANT ALL PRIVILEGES ON $dbName.* TO '$cameraUser'@'%'");
|
||||||
|
|
||||||
|
// Create camselect user
|
||||||
|
$conn->query("CREATE USER IF NOT EXISTS '$camSelectUser'@'%' IDENTIFIED BY '$camSelectPassword'");
|
||||||
|
$conn->query("GRANT SELECT ON $dbName.* TO '$camSelectUser'@'%'");
|
||||||
|
|
||||||
|
|
||||||
|
// position table
|
||||||
|
$conn->query("
|
||||||
|
CREATE TABLE IF NOT EXISTS position (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
latitude INT,
|
||||||
|
longitude INT
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
// tag table
|
||||||
|
$conn->query("
|
||||||
|
CREATE TABLE IF NOT EXISTS tag (
|
||||||
|
id BIGINT,
|
||||||
|
k VARCHAR(100),
|
||||||
|
v VARCHAR(10000),
|
||||||
|
PRIMARY KEY (id, k),
|
||||||
|
CONSTRAINT fk_position FOREIGN KEY (id) REFERENCES `position`(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
$conn->query("CREATE INDEX IF NOT EXISTS LatLon ON position (latitude, longitude)");
|
||||||
|
|
||||||
|
|
||||||
|
// sync_state table
|
||||||
|
$conn->query("
|
||||||
|
CREATE TABLE IF NOT EXISTS sync_state (
|
||||||
|
k VARCHAR(100),
|
||||||
|
v VARCHAR(100)
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
|
$result = $conn -> query("SELECT * FROM sync_state WHERE k='sequenceNumber'");
|
||||||
|
|
||||||
|
$table_count = $result -> num_rows;
|
||||||
|
|
||||||
|
if ($table_count == 0){
|
||||||
|
$conn->query("INSERT INTO sync_state (k, v) VALUES ('sequenceNumber', '0');");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Database, users, and tables created successfully.\n";
|
||||||
|
|
||||||
|
$result = $conn -> query("SELECT * FROM position limit 10");
|
||||||
|
|
||||||
|
$table_count = $result -> num_rows;
|
||||||
|
|
||||||
|
printf("Result set has %d rows.\n", $table_count);
|
||||||
|
if ($table_count == 0){
|
||||||
|
$location = "/opt/init/init.sql";
|
||||||
|
|
||||||
|
$commands = file_get_contents($location);
|
||||||
|
$conn->multi_query($commands);
|
||||||
|
echo "Inserted data.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
291
data_handler/utils/update_camera.php
Executable file
|
@ -0,0 +1,291 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
include "config.php";
|
||||||
|
|
||||||
|
$elementTypes = array();
|
||||||
|
$count = 0;
|
||||||
|
$countDelete = 0;
|
||||||
|
$countModifyDelete = 0;
|
||||||
|
$countModify = 0;
|
||||||
|
$countCreate = 0;
|
||||||
|
$mode = '';
|
||||||
|
$curNodeAttrs = null;
|
||||||
|
$curNodeTags = null;
|
||||||
|
$id = 0;
|
||||||
|
$latitude = 0;
|
||||||
|
$longitude = 0;
|
||||||
|
|
||||||
|
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
|
||||||
|
|
||||||
|
if($mysqli->connect_errno) {
|
||||||
|
echo "Error while connecting to DB : $mysqli->error \n" ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$mysqli->autocommit(FALSE);
|
||||||
|
|
||||||
|
if (! ($deleteStmt = $mysqli->prepare("DELETE FROM position WHERE id=?"))) {
|
||||||
|
echo "Error while preparing delete position statement : " . $mysqli->error ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$deleteStmt->bind_param('d', $id);
|
||||||
|
|
||||||
|
if (! ($deleteTagStmt = $mysqli->prepare("DELETE FROM tag WHERE id=?"))) {
|
||||||
|
echo "Error while preparing delete tag statement : " . $mysqli->error ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$deleteTagStmt->bind_param('d', $id);
|
||||||
|
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
if (! ($deleteStatsStmt = $mysqli->prepare("DELETE FROM statistics WHERE id=?"))) {
|
||||||
|
echo "Error while preparing delete statistics statement : " . $mysqli->error ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$deleteStatsStmt->bind_param('d', $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ($insertStmt = $mysqli->prepare("INSERT INTO position (id, latitude, longitude) VALUES (?, ?, ?)"))) {
|
||||||
|
echo "Error while preparing insert position statement : " . $mysqli->error ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$insertStmt->bind_param('dii', $id, $latitude, $longitude);
|
||||||
|
|
||||||
|
if (! ($insertTagStmt = $mysqli->prepare("INSERT INTO tag (id, k, v) VALUES (?, ?, ?)"))) {
|
||||||
|
echo "Error while preparing insert tag statement : " . $mysqli->error ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$insertTagStmt->bind_param('dss', $id, $k, $v);
|
||||||
|
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
if (! ($insertStatsStmt = $mysqli->prepare("INSERT INTO statistics (id, ts, version) VALUES (?, ?, ?)"))) {
|
||||||
|
echo "Error while preparing insert statistics statement : " . $mysqli->error ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$insertStatsStmt->bind_param('dsi', $id, $ts, $version);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printDebug() {
|
||||||
|
global $elementTypes, $count, $countDelete, $countModifyDelete, $countModify, $countCreate;
|
||||||
|
|
||||||
|
echo "== $count ==============================\n";
|
||||||
|
foreach($elementTypes as $k => $v) {
|
||||||
|
echo "$k : $v\n";
|
||||||
|
}
|
||||||
|
echo "++++++ Surveillance nodes : ++++++\n";
|
||||||
|
echo "$countDelete deletions\n";
|
||||||
|
echo "$countModifyDelete modifications --> deletions\n";
|
||||||
|
echo "$countModify modifications\n";
|
||||||
|
echo "$countCreate creations\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function printDebugCurNode() {
|
||||||
|
global $curNodeAttrs, $curNodeTags, $mode;
|
||||||
|
|
||||||
|
echo "=============\n";
|
||||||
|
echo "$mode : " . $curNodeAttrs['id'] ." (". $curNodeAttrs['lat'] . " x " . $curNodeAttrs['lon'] . ") : " . $curNodeAttrs['user'] . "\n";
|
||||||
|
if (! empty($curNodeAttrs)) {
|
||||||
|
echo " => Attributes :\n";
|
||||||
|
foreach($curNodeAttrs as $k => $v) {
|
||||||
|
echo " $k : $v\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo " => No attributes\n";
|
||||||
|
}
|
||||||
|
if (! empty($curNodeTags)) {
|
||||||
|
echo " => Tags :\n";
|
||||||
|
foreach($curNodeTags as $k => $v) {
|
||||||
|
echo " $k : $v\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo " => No tags\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startElement ($parser, $name, $attrs) {
|
||||||
|
global $elementTypes, $count, $mode, $curNodeAttrs, $curNodeTags;
|
||||||
|
$count++;
|
||||||
|
|
||||||
|
if (array_key_exists($name, $elementTypes)) {
|
||||||
|
$elementTypes[$name] += 1;
|
||||||
|
} else {
|
||||||
|
$elementTypes[$name] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name == 'modify' || $name == 'delete' or $name == 'create') {
|
||||||
|
$mode = $name;
|
||||||
|
|
||||||
|
} else if ($name == 'node') {
|
||||||
|
$curNodeAttrs = $attrs;
|
||||||
|
$curNodeTags = array();
|
||||||
|
|
||||||
|
} else if ($name == 'tag') {
|
||||||
|
$curNodeTags[$attrs['k']] = $attrs['v'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endElement ($parser, $name) {
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
global $mode, $deleteStmt, $deleteTagStmt, $deleteStatsStmt, $insertStmt, $insertTagStmt, $insertStatsStmt, $selectStmt, $id, $latitude, $longitude, $k, $v, $ts, $version, $curNodeAttrs, $curNodeTags, $mysqli, $countDelete, $countModifyDelete, $countModify, $countCreate;
|
||||||
|
} else {
|
||||||
|
global $mode, $deleteStmt, $deleteTagStmt, $insertStmt, $insertTagStmt, $selectStmt, $id, $latitude, $longitude, $k, $v, $curNodeAttrs, $curNodeTags, $mysqli, $countDelete, $countModifyDelete, $countModify, $countCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name == 'modify' || $name == 'delete' or $name == 'create') {
|
||||||
|
$mode = '';
|
||||||
|
} else if ($name == 'node') {
|
||||||
|
if ($mode == 'delete') {
|
||||||
|
$id = $curNodeAttrs['id'];
|
||||||
|
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
if (! $deleteStatsStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting statistics $id : ". $deleteStatsStmt->error . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $deleteTagStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting tags $id : ". $deleteTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
if (! $deleteStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting $id : ". $deleteStmt->error . "\n";
|
||||||
|
}
|
||||||
|
if ($deleteStmt->affected_rows > 0) {
|
||||||
|
$countDelete++;
|
||||||
|
$mysqli->commit();
|
||||||
|
printDebugCurNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ($mode == 'modify' || $mode == 'create') {
|
||||||
|
if (! empty($curNodeTags)
|
||||||
|
&& array_key_exists('man_made', $curNodeTags)
|
||||||
|
&& $curNodeTags['man_made'] == 'surveillance') {
|
||||||
|
|
||||||
|
printDebugCurNode();
|
||||||
|
|
||||||
|
$id = $curNodeAttrs['id'];
|
||||||
|
|
||||||
|
if ($mode == 'modify') {
|
||||||
|
$countModify++;
|
||||||
|
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
if (! $deleteStatsStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting statistics $id for modification : ". $deleteStatsStmt->error . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $deleteTagStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting tags $id for modification : ". $deleteTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
if (! $deleteStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting $id for modification : ". $deleteStmt->error . "\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$countCreate++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$latitude = (int) ($curNodeAttrs['lat'] * 10000000);
|
||||||
|
$longitude = (int) ($curNodeAttrs['lon'] * 10000000);
|
||||||
|
|
||||||
|
if (! $insertStmt->execute()) {
|
||||||
|
echo "***** Error : inserting $id ($latitude x $longitude) : ". $insertStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = 'lat';
|
||||||
|
$v = $curNodeAttrs['lat'];
|
||||||
|
if (! $insertTagStmt->execute()) {
|
||||||
|
echo "***** Error : inserting latitude $v for $id : ". $insertTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = 'lon';
|
||||||
|
$v = $curNodeAttrs['lon'];
|
||||||
|
if (! $insertTagStmt->execute()) {
|
||||||
|
echo "***** Error : inserting longitude $v for $id : ". $insertTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = 'userid';
|
||||||
|
$v = $curNodeAttrs['user'];
|
||||||
|
if (! $insertTagStmt->execute()) {
|
||||||
|
echo "***** Error : inserting user $v for $id : ". $insertTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = 'version';
|
||||||
|
$v = $curNodeAttrs['version'];
|
||||||
|
if (! $insertTagStmt->execute()) {
|
||||||
|
echo "***** Error : inserting version $v for $id : ". $insertTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = 'timestamp';
|
||||||
|
$v = $curNodeAttrs['timestamp'];
|
||||||
|
if (! $insertTagStmt->execute()) {
|
||||||
|
echo "***** Error : inserting timestamp $v for $id : ". $insertTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($curNodeTags as $k => $v) {
|
||||||
|
if (! $insertTagStmt->execute()) {
|
||||||
|
echo "***** Error : inserting tag $k => $v for $id : ". $insertTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
$ts = $curNodeAttrs['timestamp'];
|
||||||
|
$version = $curNodeAttrs['version'];
|
||||||
|
if (! $insertStatsStmt->execute()) {
|
||||||
|
echo "***** Error : inserting ts $ts, version $version for $id : ". $insertStatsStmt->error . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mysqli->commit();
|
||||||
|
} else if ($mode == 'modify') {
|
||||||
|
// delete former surveillance nodes that were modified to non-surveillance nodes
|
||||||
|
$id = $curNodeAttrs['id'];
|
||||||
|
|
||||||
|
if (USE_STATISTICS) {
|
||||||
|
if (! $deleteStatsStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting statistics $id for non-surveillance node : ". $deleteStatsStmt->error . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $deleteTagStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting tags $id for non-surveillance node : ". $deleteTagStmt->error . "\n";
|
||||||
|
}
|
||||||
|
if (! $deleteStmt->execute()) {
|
||||||
|
echo "***** Error : Deleting $id for non-surveillance node : ". $deleteStmt->error . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deleteStmt->affected_rows > 0) {
|
||||||
|
$countModifyDelete++;
|
||||||
|
$mysqli->commit();
|
||||||
|
printDebugCurNode();
|
||||||
|
echo " ==> delete because modified to non-surveillance node\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$curNodeAttrs = null;
|
||||||
|
$curNodeTags = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file="change_file.osc";
|
||||||
|
|
||||||
|
$xml_parser = xml_parser_create();
|
||||||
|
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false);
|
||||||
|
xml_set_element_handler($xml_parser, "startElement", "endElement");
|
||||||
|
|
||||||
|
if (!($fp = fopen($file, "r"))) {
|
||||||
|
die("could not open XML input");
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($data = fread($fp, 4096)) {
|
||||||
|
if (!xml_parse($xml_parser, $data, feof($fp))) {
|
||||||
|
die(sprintf("XML error: %s at line %d",
|
||||||
|
xml_error_string(xml_get_error_code($xml_parser)),
|
||||||
|
xml_get_current_line_number($xml_parser)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql="UPDATE sync_state SET v='" . $_SERVER['argv'][1] ."' WHERE k='sequenceNumber';";
|
||||||
|
$mysqli->query($sql);
|
||||||
|
$mysqli->commit();
|
||||||
|
|
||||||
|
printDebug();
|
||||||
|
$mysqli->close();
|
||||||
|
|
||||||
|
?>
|
93
data_handler/utils/update_camera.sh
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
exedir=$(cd `dirname "$0"`; pwd)
|
||||||
|
|
||||||
|
REPLICATE_URL=`grep "REPLICATE_URL" "$exedir/config.php" | sed -e 's/.*http/http/' -e 's#...$##' `
|
||||||
|
|
||||||
|
if [ -e "/var/lock/update_camera" ]
|
||||||
|
then
|
||||||
|
# Maybe an other update is running
|
||||||
|
otherPid=$(cat "/var/lock/update_camera")
|
||||||
|
count=$(ps $otherPid | grep -c `basename "$0"`)
|
||||||
|
|
||||||
|
if [ $count -gt 0 ]
|
||||||
|
then
|
||||||
|
echo "$0 is running yet. Exiting." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OK. We can update the database...
|
||||||
|
echo $$ > "/var/lock/update_camera"
|
||||||
|
|
||||||
|
exeDir=$(cd `dirname "$0"`; pwd);
|
||||||
|
cd "$exeDir"
|
||||||
|
|
||||||
|
if [ -e "state.txt" ]
|
||||||
|
then
|
||||||
|
rm "state.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#TODO get state from db
|
||||||
|
# Read the last update timestamp
|
||||||
|
# lastTimestamp=$(grep "^timestamp=" "lastState.txt" | cut -d'=' -f2-)
|
||||||
|
# lastSeqNum=$(grep "^sequenceNumber=" "lastState.txt" | cut -d'=' -f2-)
|
||||||
|
|
||||||
|
lastSeqNum=$(php get_sync_state.php)
|
||||||
|
|
||||||
|
curl -L "$REPLICATE_URL/state.txt" -o state.txt
|
||||||
|
|
||||||
|
newTimestamp=$(grep "^timestamp=" "state.txt" | cut -d'=' -f2-)
|
||||||
|
newSeqNum=$(grep "^sequenceNumber=" "state.txt" | cut -d'=' -f2-)
|
||||||
|
|
||||||
|
if [ $newSeqNum -eq $lastSeqNum ]
|
||||||
|
then
|
||||||
|
echo "No new file to be processed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir logs
|
||||||
|
curSeqNum=$lastSeqNum
|
||||||
|
while [ $curSeqNum -lt $newSeqNum ]
|
||||||
|
do
|
||||||
|
curSeqNum=$(( $curSeqNum + 1 ))
|
||||||
|
|
||||||
|
logFileName="logs/log.$curSeqNum"
|
||||||
|
|
||||||
|
if [ -e "change_file.osc" ]
|
||||||
|
then
|
||||||
|
rm "change_file.osc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "change_file.osc.gz" ]
|
||||||
|
then
|
||||||
|
rm "change_file.osc.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo `date "+%d/%m/%Y %H:%M"`" - Start processing sequence number $curSeqNum" > "$logFileName"
|
||||||
|
|
||||||
|
targetDirName=`echo "000000000$curSeqNum" | sed 's#.*\(...\)\(...\)\(...\)$#\1/\2/\3.osc.gz#'`
|
||||||
|
targetDirName="$REPLICATE_URL/$targetDirName"
|
||||||
|
|
||||||
|
curl -L "$targetDirName" -o change_file.osc.gz 2>&1 | tee -a $logFileName
|
||||||
|
if [ $? -gt 0 ]
|
||||||
|
then
|
||||||
|
echo "Error during recovery of $targetDirName" | tee -a $logFileName
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
gunzip change_file.osc.gz 2>&1 | tee -a $logFileName
|
||||||
|
if [ $? -gt 0 ]
|
||||||
|
then
|
||||||
|
echo "Error during decompression of $targetDirName" | tee -a $logFileName
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
php update_camera.php $curSeqNum 2>&1 | tee -a $logFileName
|
||||||
|
|
||||||
|
targetDirName=`echo "$targetDirName" | sed 's/.osc.gz$/.state.txt/'`
|
||||||
|
|
||||||
|
curl -L "$targetDirName" -o lastState.txt 2>&1 | tee -a $logFileName
|
||||||
|
echo `date "+%d/%m/%Y %H:%M"`" - Finish processing sequence number $curSeqNum" | tee -a "$logFileName"
|
||||||
|
done
|
||||||
|
|
||||||
|
rm "/var/lock/update_camera"
|
4
data_handler_python/Containerfile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
FROM quay.io/fedora/fedora-minimal:43
|
||||||
|
RUN microdnf install -y python-pip && microdnf clean all && pip install OSMPythonTools
|
||||||
|
|
||||||
|
COPY ./lookup_country.py /opt/
|
7
data_handler_python/lookup_country.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from OSMPythonTools.nominatim import Nominatim
|
||||||
|
|
||||||
|
|
||||||
|
nominatim = Nominatim()
|
||||||
|
heidelberg = nominatim.query(49.4093582, 8.694724, reverse=True, zoom=10)
|
||||||
|
|
||||||
|
print(heidelberg.address()['country_code'])
|
12
data_handler_python/test.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
import xml.etree.cElementTree as ET
|
||||||
|
import gzip
|
||||||
|
import requests
|
||||||
|
|
||||||
|
r = requests.get('https://planet.openstreetmap.org/replication/minute/000/000/001.osc.gz')
|
||||||
|
|
||||||
|
data = gzip.decompress(r.content)
|
||||||
|
|
||||||
|
tree = ET.fromstring(data)
|
||||||
|
print(tree.findall("./."))
|
49
docker-compose.yml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mariadb:12.0.2
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword # ${{secrets.MYSQL_ROOT_PASSWORD}}
|
||||||
|
MYSQL_DATABASE: camera # ${{secrets.MYSQL_DATABASE}}
|
||||||
|
MYSQL_USER: camera # ${{secrets.MYSQL_USER}}
|
||||||
|
MYSQL_PASSWORD: camerapassword # ${{secrets.MYSQL_PASSWORD}}
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-uroot", "-prootpassword"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
start_period: 30s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: git.hamburg.ccc.de/ccchh/sunders/web:latest
|
||||||
|
environment:
|
||||||
|
MYSQL_HOST: db
|
||||||
|
MYSQL_DB: camera # ${{secrets.MYSQL_DATABASE}}
|
||||||
|
MYSQL_USER: camera_select # ${{secrets.CAMERA_SELECT_USER}}
|
||||||
|
MYSQL_PASSWORD: camera_selectpassword # ${{secrets.CAMERA_SELECT_USER_PASSWORD}}
|
||||||
|
DEFAULT_ZOOM: 12
|
||||||
|
DEFAULT_LAT: 0
|
||||||
|
DEFAULT_LON: 0
|
||||||
|
DEFAULT_LANGUAGE: en
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
depends_on:
|
||||||
|
data_handler:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
data_handler:
|
||||||
|
image: git.hamburg.ccc.de/ccchh/sunders/data_handler:latest
|
||||||
|
environment:
|
||||||
|
MYSQL_HOST: db
|
||||||
|
MYSQL_DB: camera # ${{secrets.MYSQL_DATABASE}}
|
||||||
|
MYSQL_USER: root # ${{secrets.MYSQL_USER}}
|
||||||
|
MYSQL_PASSWORD: rootpassword # ${{secrets.MYSQL_ROOT_PASSWORD}}
|
||||||
|
CAMERA_USER: camera # ${{secrets.CAMERA_USER}}
|
||||||
|
CAMERA_USER_PASSWORD: camerapassword # ${{secrets.CAMERA_USER_PASSWORD}}
|
||||||
|
CAMERA_SELECT_USER: camera_select # ${{secrets.CAMERA_SELECT_USER}}
|
||||||
|
CAMERA_SELECT_USER_PASSWORD: camera_selectpassword # ${{secrets.CAMERA_SELECT_USER_PASSWORD}}
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
|
@ -1,7 +1,7 @@
|
||||||
create database camera;
|
create database camera;
|
||||||
use camera;
|
use camera;
|
||||||
|
|
||||||
grant all privileges on camera.* to camera@localhost identified by 'xxxxxxxx';
|
grant all privileges on camera.* to camera@localhost identified by 'asdf';
|
||||||
grant select on camera.* to camselect@localhost;
|
grant select on camera.* to camselect@localhost;
|
||||||
|
|
||||||
CREATE TABLE position (
|
CREATE TABLE position (
|
||||||
|
|
|
@ -15,7 +15,7 @@ $id = 0;
|
||||||
$latitude = 0;
|
$latitude = 0;
|
||||||
$longitude = 0;
|
$longitude = 0;
|
||||||
|
|
||||||
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWD, MYSQL_DB);
|
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
|
||||||
|
|
||||||
if($mysqli->connect_errno) {
|
if($mysqli->connect_errno) {
|
||||||
echo "Error while connecting to DB : $mysqli->error \n" ;
|
echo "Error while connecting to DB : $mysqli->error \n" ;
|
||||||
|
|
|
@ -4,7 +4,7 @@ include "config.php";
|
||||||
|
|
||||||
if (USE_STATISTICS) {
|
if (USE_STATISTICS) {
|
||||||
|
|
||||||
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWD, MYSQL_DB);
|
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
|
||||||
|
|
||||||
if ($mysqli->connect_errno) {
|
if ($mysqli->connect_errno) {
|
||||||
echo "Error while connecting to DB : ".$mysqli->error." \n" ;
|
echo "Error while connecting to DB : ".$mysqli->error." \n" ;
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"config:recommended"
|
|
||||||
]
|
|
||||||
}
|
|
4
web/Containerfile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
FROM docker.io/library/php:apache
|
||||||
|
|
||||||
|
RUN docker-php-ext-install mysqli bcmath
|
||||||
|
COPY ./www/* /var/www/html
|
BIN
web/www/sunders/Leaflet/images/layers-2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
web/www/sunders/Leaflet/images/layers.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
web/www/sunders/Leaflet/images/marker-icon-2x.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
web/www/sunders/Leaflet/images/marker-icon.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
web/www/sunders/Leaflet/images/marker-shadow.png
Normal file
After Width: | Height: | Size: 618 B |
14419
web/www/sunders/Leaflet/leaflet-src.esm.js
Normal file
1
web/www/sunders/Leaflet/leaflet-src.esm.js.map
Normal file
14512
web/www/sunders/Leaflet/leaflet-src.js
Normal file
1
web/www/sunders/Leaflet/leaflet-src.js.map
Normal file
|
@ -45,7 +45,10 @@
|
||||||
}
|
}
|
||||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||||
.leaflet-container .leaflet-overlay-pane svg,
|
.leaflet-container .leaflet-overlay-pane svg {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
.leaflet-container .leaflet-marker-pane img,
|
.leaflet-container .leaflet-marker-pane img,
|
||||||
.leaflet-container .leaflet-shadow-pane img,
|
.leaflet-container .leaflet-shadow-pane img,
|
||||||
.leaflet-container .leaflet-tile-pane img,
|
.leaflet-container .leaflet-tile-pane img,
|
||||||
|
@ -53,6 +56,13 @@
|
||||||
.leaflet-container .leaflet-tile {
|
.leaflet-container .leaflet-tile {
|
||||||
max-width: none !important;
|
max-width: none !important;
|
||||||
max-height: none !important;
|
max-height: none !important;
|
||||||
|
width: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container img.leaflet-tile {
|
||||||
|
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||||
|
mix-blend-mode: plus-lighter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-container.leaflet-touch-zoom {
|
.leaflet-container.leaflet-touch-zoom {
|
||||||
|
@ -166,9 +176,6 @@
|
||||||
|
|
||||||
/* zoom and fade animations */
|
/* zoom and fade animations */
|
||||||
|
|
||||||
.leaflet-fade-anim .leaflet-tile {
|
|
||||||
will-change: opacity;
|
|
||||||
}
|
|
||||||
.leaflet-fade-anim .leaflet-popup {
|
.leaflet-fade-anim .leaflet-popup {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transition: opacity 0.2s linear;
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
@ -183,9 +190,10 @@
|
||||||
-ms-transform-origin: 0 0;
|
-ms-transform-origin: 0 0;
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
}
|
}
|
||||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
svg.leaflet-zoom-animated {
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
@ -251,14 +259,11 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
|
|
||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
outline: 0;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
.leaflet-container a {
|
.leaflet-container a {
|
||||||
color: #0078A8;
|
color: #0078A8;
|
||||||
}
|
}
|
||||||
.leaflet-container a.leaflet-active {
|
|
||||||
outline: 2px solid orange;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-box {
|
.leaflet-zoom-box {
|
||||||
border: 2px dotted #38f;
|
border: 2px dotted #38f;
|
||||||
background: rgba(255,255,255,0.5);
|
background: rgba(255,255,255,0.5);
|
||||||
|
@ -267,7 +272,10 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
|
|
||||||
/* general typography */
|
/* general typography */
|
||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -277,8 +285,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.leaflet-bar a,
|
.leaflet-bar a {
|
||||||
.leaflet-bar a:hover {
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
width: 26px;
|
width: 26px;
|
||||||
|
@ -295,7 +302,8 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.leaflet-bar a:hover {
|
.leaflet-bar a:hover,
|
||||||
|
.leaflet-bar a:focus {
|
||||||
background-color: #f4f4f4;
|
background-color: #f4f4f4;
|
||||||
}
|
}
|
||||||
.leaflet-bar a:first-child {
|
.leaflet-bar a:first-child {
|
||||||
|
@ -385,6 +393,8 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
}
|
}
|
||||||
.leaflet-control-layers label {
|
.leaflet-control-layers label {
|
||||||
display: block;
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-size: 1.08333em;
|
||||||
}
|
}
|
||||||
.leaflet-control-layers-separator {
|
.leaflet-control-layers-separator {
|
||||||
height: 0;
|
height: 0;
|
||||||
|
@ -393,7 +403,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Default icon URLs */
|
/* Default icon URLs */
|
||||||
.leaflet-default-icon-path {
|
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||||
background-image: url(images/marker-icon.png);
|
background-image: url(images/marker-icon.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,23 +412,27 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
|
|
||||||
.leaflet-container .leaflet-control-attribution {
|
.leaflet-container .leaflet-control-attribution {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
background: rgba(255, 255, 255, 0.7);
|
background: rgba(255, 255, 255, 0.8);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.leaflet-control-attribution,
|
.leaflet-control-attribution,
|
||||||
.leaflet-control-scale-line {
|
.leaflet-control-scale-line {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
.leaflet-control-attribution a {
|
.leaflet-control-attribution a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.leaflet-control-attribution a:hover {
|
.leaflet-control-attribution a:hover,
|
||||||
|
.leaflet-control-attribution a:focus {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.leaflet-container .leaflet-control-attribution,
|
.leaflet-attribution-flag {
|
||||||
.leaflet-container .leaflet-control-scale {
|
display: inline !important;
|
||||||
font-size: 11px;
|
vertical-align: baseline !important;
|
||||||
|
width: 1em;
|
||||||
|
height: 0.6669em;
|
||||||
}
|
}
|
||||||
.leaflet-left .leaflet-control-scale {
|
.leaflet-left .leaflet-control-scale {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
@ -431,14 +445,11 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
padding: 2px 5px 1px;
|
padding: 2px 5px 1px;
|
||||||
font-size: 11px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
|
||||||
-moz-box-sizing: border-box;
|
-moz-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
background: #fff;
|
text-shadow: 1px 1px #fff;
|
||||||
background: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
}
|
||||||
.leaflet-control-scale-line:not(:first-child) {
|
.leaflet-control-scale-line:not(:first-child) {
|
||||||
border-top: 2px solid #777;
|
border-top: 2px solid #777;
|
||||||
|
@ -474,17 +485,22 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
.leaflet-popup-content {
|
.leaflet-popup-content {
|
||||||
margin: 13px 19px;
|
margin: 13px 24px 13px 20px;
|
||||||
line-height: 1.4;
|
line-height: 1.3;
|
||||||
|
font-size: 13px;
|
||||||
|
font-size: 1.08333em;
|
||||||
|
min-height: 1px;
|
||||||
}
|
}
|
||||||
.leaflet-popup-content p {
|
.leaflet-popup-content p {
|
||||||
margin: 18px 0;
|
margin: 17px 0;
|
||||||
|
margin: 1.3em 0;
|
||||||
}
|
}
|
||||||
.leaflet-popup-tip-container {
|
.leaflet-popup-tip-container {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
margin-top: -1px;
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -495,6 +511,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
|
|
||||||
margin: -10px auto 0;
|
margin: -10px auto 0;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
-webkit-transform: rotate(45deg);
|
-webkit-transform: rotate(45deg);
|
||||||
-moz-transform: rotate(45deg);
|
-moz-transform: rotate(45deg);
|
||||||
|
@ -511,24 +528,21 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 4px 4px 0 0;
|
|
||||||
border: none;
|
border: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 18px;
|
width: 24px;
|
||||||
height: 14px;
|
height: 24px;
|
||||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||||
color: #c3c3c3;
|
color: #757575;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: bold;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||||
color: #999;
|
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||||
|
color: #585858;
|
||||||
}
|
}
|
||||||
.leaflet-popup-scrolled {
|
.leaflet-popup-scrolled {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||||
|
@ -541,9 +555,6 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||||
}
|
}
|
||||||
.leaflet-oldie .leaflet-popup-tip-container {
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .leaflet-control-zoom,
|
.leaflet-oldie .leaflet-control-zoom,
|
||||||
.leaflet-oldie .leaflet-control-layers,
|
.leaflet-oldie .leaflet-control-layers,
|
||||||
|
@ -578,7 +589,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
.leaflet-tooltip.leaflet-clickable {
|
.leaflet-tooltip.leaflet-interactive {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
@ -638,3 +649,13 @@ svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
margin-left: -12px;
|
margin-left: -12px;
|
||||||
border-right-color: #fff;
|
border-right-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Printing */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Prevent printers from removing background-images of controls. */
|
||||||
|
.leaflet-control {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
6
web/www/sunders/Leaflet/leaflet.js
Normal file
1
web/www/sunders/Leaflet/leaflet.js.map
Normal file
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
error_reporting(0);
|
||||||
// Convert the content of the symbology JSON file to HTML.
|
// Convert the content of the symbology JSON file to HTML.
|
||||||
function addListSymbology($jsonPath, $i18n, $i18nDefault) {
|
function addListSymbology($jsonPath, $i18n, $i18nDefault) {
|
||||||
global $pathToWebFolder;
|
global $pathToWebFolder;
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
error_reporting(0);
|
||||||
include $pathToWebFolder.'config.php';
|
include $pathToWebFolder.'config.php';
|
||||||
|
|
||||||
define('MAX_POINTS_FOR_QUICKHULL', 3000);
|
define('MAX_POINTS_FOR_QUICKHULL', 3000);
|
||||||
|
@ -266,7 +266,7 @@
|
||||||
$divDiag2 = ($divWidth * $divWidth) + ($divHeight * $divHeight);
|
$divDiag2 = ($divWidth * $divWidth) + ($divHeight * $divHeight);
|
||||||
|
|
||||||
/* Connect to database */
|
/* Connect to database */
|
||||||
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWD, MYSQL_DB);
|
$mysqli = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
|
||||||
if($mysqli->connect_errno) {
|
if($mysqli->connect_errno) {
|
||||||
header('Content-type: application/json');
|
header('Content-type: application/json');
|
||||||
$result = '{"error":"error while connecting to db : ' . $mysqli->error . '"}';
|
$result = '{"error":"error while connecting to db : ' . $mysqli->error . '"}';
|
15
web/www/sunders/config.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
define('DEFAULT_ZOOM', getenv('DEFAULT_ZOOM') ?: 11);
|
||||||
|
define('DEFAULT_LAT', getenv('DEFAULT_LAT') ?: 53.5550);
|
||||||
|
define('DEFAULT_LON', getenv('DEFAULT_LON') ?: 10.0099);
|
||||||
|
define('DEFAULT_LANGUAGE', getenv('DEFAULT_LANGUAGE') ?: 'en');
|
||||||
|
define('DEFAULT_PIE', 'country');
|
||||||
|
define('DEFAULT_TIME', 'single');
|
||||||
|
|
||||||
|
define('MYSQL_HOST', getenv('MYSQL_HOST') ?: '');
|
||||||
|
define('MYSQL_DB', getenv('MYSQL_DB') ?: '');
|
||||||
|
define('MYSQL_USER', getenv('MYSQL_USER') ?: '');
|
||||||
|
define('MYSQL_PASSWORD', getenv('MYSQL_PASSWORD') ?: '');
|
||||||
|
|
||||||
|
define('USE_STATISTICS', false);
|
||||||
|
?>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1,003 B After Width: | Height: | Size: 1,003 B |