Skip to content

Commit

Permalink
Added simple api to launch scripts, docker / compose test files
Browse files Browse the repository at this point in the history
  • Loading branch information
fxi committed Jul 10, 2024
1 parent b09fe26 commit 7100a05
Show file tree
Hide file tree
Showing 17 changed files with 589 additions and 179 deletions.
File renamed without changes.
File renamed without changes.
2 changes: 0 additions & 2 deletions 01_get_data/main.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,3 @@ compile_processed_data(
mostRecent = TRUE
)


browser()
6 changes: 4 additions & 2 deletions 04_travel_time/project_create.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ source("global.R")
source("/helpers/find_inaccessmod_layer.R")
source("/helpers/get_location.R")

location <- get_location()
# Get location from argument or default
location_arg <- get_arg("--location")
location <- if (!is.null(location_arg)) location_arg else get_location()

location_path <- "/data/location"
project_name <- sprintf("project_gpp_%s", location)
project_db <- file.path("/data/dbgrass/", project_name)
Expand All @@ -18,7 +21,6 @@ dem_path <- find_inaccessmod_layer(
"rDEM_pr.tif",
copy = TRUE
)
browser()

amGrassNS(
mapset = "demo",
Expand Down
145 changes: 101 additions & 44 deletions 04_travel_time/travel_time.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,31 @@ source("/helpers/find_inaccessmod_layer.R")
source("/helpers/get_location.R")
source("/helpers/pop_vs_traveltime.R")

location <- get_location()

location <- get_arg("--location", default = get_location())
scenario <- get_arg("--scenario", default = NULL)

location_path <- "/data/location"
project_name <- sprintf("project_gpp_%s", location)

conf <- amAnalysisReplayParseConf(
"/data/config/default.json"
)
conf$location <- project_name
conf$mapset <- project_name

if (isNotEmpty(scenario)) {
conf$tableScenario <- jsonlite::fromJSON(scenario)
}

output_folder <- file.path(location_path, location, "output")
output_travel_time <- file.path(output_folder, "travel_time.tif")
output_nearest <- file.path(output_folder, "travel_nearest.tif")
output_travel_time <- file.path(output_folder, "travel_time.tif")
output_travel_time_wgs84 <- file.path(output_folder, "travel_time_wgs84.tif")
output_pop_vs_time_data <- file.path(output_folder, "pop_vs_traveltime.csv")
output_pop_vs_time_plot <- file.path(output_folder, "pop_vs_traveltime.pdf")
proj_4 <- NULL
pop_vs_time <- data.frame()

dir.create(output_folder, showWarnings = FALSE, recursive = TRUE)

Expand All @@ -36,18 +51,6 @@ facilities_path <- find_inaccessmod_layer(
"vFacilities_pr.shp",
copy = TRUE
)

#
# Default config should be updated
# - Empty HF table (default to all, no valdiation)
# - cucrent mapset/location
#
conf <- amAnalysisReplayParseConf(
"/data/config/default.json"
)
conf$location <- project_name
conf$mapset <- project_name

#
# Start AccessMod Session
#
Expand All @@ -59,32 +62,38 @@ amGrassNS(
# - match AccessMod classes in /www/dictionary/classes.json
# - use two underscore to separate tags from class, and
# one between tags e.g. vFacility__test_a

execGRASS(
"r.in.gdal",
band = 1,
input = landcover_merged_path,
output = "rLandCoverMerged__pr",
title = "rLandCoverMerged__pr",
flags = c("overwrite", "quiet")
)

execGRASS(
"r.in.gdal",
band = 1,
input = population_path,
output = "rPopulation__pr",
title = "rPopulation__pr",
flags = c("overwrite", "quiet")
)

execGRASS("v.in.ogr",
flags = c("overwrite", "w", "2"), # overwrite, lowercase, 2d only,
input = facilities_path,
key = "cat",
output = "vFacility__pr",
snap = 0.0001
)
if (!amRastExsit("rLandCoverMerged__pr")) {
execGRASS(
"r.in.gdal",
band = 1,
input = landcover_merged_path,
output = "rLandCoverMerged__pr",
title = "rLandCoverMerged__pr",
flags = c("overwrite", "quiet")
)
}

if (!amRastExsit("rPopulation__pr")) {
execGRASS(
"r.in.gdal",
band = 1,
input = population_path,
output = "rPopulation__pr",
title = "rPopulation__pr",
flags = c("overwrite", "quiet")
)
}


if (!amVectExsit("rLandCoverMerged__pr")) {
execGRASS("v.in.ogr",
flags = c("overwrite", "w", "2"), # overwrite, lowercase, 2d only,
input = facilities_path,
key = "cat",
output = "vFacility__pr",
snap = 0.0001
)
}

exportedDirs <- amAnalysisReplayExec(conf,
exportDirectory = output_folder
Expand All @@ -96,14 +105,62 @@ amGrassNS(
# - table -> output
# - plot -> output
#
popVsTime <- amGetRasterStatZonal(
pop_vs_time <- amGetRasterStatZonal(
"rPopulation__pr",
"rTravelTime__pr"
)

write.csv(popVsTime, output_pop_vs_time_data)
plot_cumulative_sum(popVsTime, output_pop_vs_time_plot)
write.csv(pop_vs_time, output_pop_vs_time_data)
plot_cumulative_sum(pop_vs_time, output_pop_vs_time_plot)


#
# Export tt as tif
#
proj_4 <- paste(execGRASS("g.proj", flags = c("j"), intern = TRUE), collapse = " ")

execGRASS("r.colors",
flags = c("n"),
map = "rTravelTime__pr",
color = "viridis"
)


execGRASS("r.out.gdal",
flags = c("overwrite", "f", "m"),
input = "rTravelTime__pr",
output = output_travel_time,
format = "GTiff",
createopt = "TFW=YES"
)
execGRASS("r.out.gdal",
flags = c("overwrite", "f", "c", "m"),
input = "rNearest__pr",
output = output_nearest,
format = "GTiff",
createopt = "TFW=YES"
)

tmp_tif <- raster(output_travel_time)
tmp_tif_reproj <- projectRaster(tmp_tif, crs = "+init=epsg:4326")
writeRaster(
tmp_tif_reproj,
output_travel_time_wgs84,
format = "GTiff",
overwrite = TRUE
)
}
)

print(sprintf("Data exported in %s", output_folder))
result <- list(
pop_vs_time_data = output_pop_vs_time_data,
pop_vs_time_plot = output_pop_vs_time_plot,
travel_time = output_travel_time,
travel_time_wgs84 = output_travel_time_wgs84,
nearest = output_nearest,
proj_4 = proj_4,
thresholds = pop_vs_time$zone
)


print(jsonlite::toJSON(result, auto_unbox = TRUE))
19 changes: 19 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM fredmoser/accessmod:5.8.3-alpha.1

RUN apk add --no-cache nodejs npm

# context = parent !
COPY helpers /helpers
COPY 04_travel_time /run
COPY api /api


WORKDIR /api
RUN npm install


VOLUME /data

EXPOSE 3000

CMD ["node", "app.js"]
72 changes: 72 additions & 0 deletions api/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import express from "express";
import { getListLocations, computeTravelTime } from "./helpers.js";
import { TifContour } from "./contour.js";
const app = express();
const port = process.env.NODE_ENV === "development" ? 3030 : 3000;

import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

app.use(express.json());
app.use("/data/location", express.static("/data/location"));

app.get("/data/location/*", (req, res) => {
const filePath = path.join(__dirname, "data/location", req.params[0]);
res.sendFile(filePath, (err) => {
if (err) {
res.status(404).send(`File not found at ${filePath}`);
}
});
});

// GET endpoint to list available locations
app.get("/get_list_locations", async (_, res) => {
try {
const locations = await getListLocations();
res.json(locations);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// POST endpoint to compute travel time
app.post("/compute_travel_time", async (req, res) => {
const { location, scenario, add_contours } = req.body;

try {
const locations = await getListLocations();

if (!locations.includes(location)) {
throw new Error(
`Location ${location} is not yet available, come back later`
);
}

const output = await computeTravelTime(location, scenario);

if (add_contours) {
const cc = new TifContour({
thresholds: output.thresholds,
raster: output.travel_time,
proj4: output.proj_4,
});
output.contours = await cc.render();
}

res.json({
message: "Travel time computation completed",
data: output,
});
} catch (error) {
debugger;
console.error("Error computing travel time:", error);
res.status(500).json({ error: "Error running AccessMod scripts" });
}
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
8 changes: 8 additions & 0 deletions api/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

docker build --push \
--platform linux/amd64,linux/arm64 \
-t fredmoser/accessmod_api:latest \
-f ./Dockerfile \
..

55 changes: 55 additions & 0 deletions api/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
services:
accessmod_api:
image: fredmoser/accessmod_api:latest
container_name: accessmod_api
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000/get_list_locations"]
interval: 5s
timeout: 60s
retries: 10
start_period: 10s
ports:
- "3000:3000"
volumes:
- /home/accessmod/data:/data
networks:
- traefik-network

traefik:
image: traefik:3.0.4
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- traefik-volume:/etc/traefik
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme.json"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--log.level=INFO"
- "--accesslog=true"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"

labels:
- "traefik.enable=true"
- "traefik.http.routers.accessmod.rule=Host(`accessmod.mapx.org`)"
- "traefik.http.routers.accessmod.entrypoints=websecure"
- "traefik.http.routers.accessmod.tls=true"
- "traefik.http.routers.accessmod.tls.certresolver=letsencrypt"
- "traefik.http.services.accessmod.loadbalancer.server.port=3000"

networks:
traefik-network:
external: true

volumes:
traefik-volume:
Loading

0 comments on commit 7100a05

Please sign in to comment.