Project: Abidjan Multimodal Transport Network
Maintained by: Transport for Cairo (TfC)
Contact: info@transportforcairo.com
Run an OpenTripPlanner (OTP) 1.4.0 instance for Abidjan with zero installs. The bundle includes a portable Java runtime, your data, and scripts. Analyst mode (isochrones) is enabled.
abidjan-otp/
├─ otp.jar
├─ jre/ # portable Java (JRE/JDK)
├─ data/
│ ├─ abidjan.osm.pbf
│ └─ abidjan.gtfs.zip
├─ graphs/ # built graph (auto-created)
├─ config/
│ ├─ build-config.json
│ └─ router-config.json
├─ web/ # your isochrones web app (index.html, js, css)
├─ proxy/
│ ├─ Caddyfile
│ ├─ caddy.exe # Windows binary (no install)
│ ├─ caddy # macOS/Linux binary (no install, chmod +x)
│ ├─ start-proxy.bat
│ └─ start-proxy.sh
└─ scripts/
├─ build-graph.bat / build-graph.sh
├─ start-otp.bat / start-otp.sh
└─ start-all.bat # NEW: combined starter (Windows)
start-all.bat will:
- Build the graph if missing.
- Start OTP 1.4.0 in Analyst mode (auto-detects Java 8 vs 11/17 and adds module flags when needed).
- Start the local proxy at http://localhost:3000 which serves the web app and proxies /otp to OTP with CORS headers.
Usage: double-click scripts/start-all.bat, then open http://localhost:3000/
scripts\build-graph.bat
(or on macOS/Linux: scripts/build-graph.sh)
scripts\start-otp.bat
Then (optional if you want same-origin+CORS solved):
proxy\start-proxy.bat # then open http://localhost:3000
- OTP 1.4.0 was built for Java 8. It also runs on Java 11/17 if you add Java module opens flags.
- The bundled scripts auto-detect Java version:
- On Java 8: no extra flags.
- On Java 11/17: adds:
--add-opens=java.base/java.io=ALL-UNNAMED--add-opens=java.base/java.lang=ALL-UNNAMED--add-opens=java.base/java.util=ALL-UNNAMED--add-opens=java.base/sun.nio.ch=ALL-UNNAMED
If you switch Java, rebuild the graph with the same otp.jar you run.
OTP 1.4.0 exposes the isochrone endpoint after starting in Analyst mode:
GET /otp/routers/default/isochrone?fromPlace=lon,lat&mode=WALK,TRANSIT&date=YYYY-MM-DD&time=HH:MM:SS&cutoffSec=900&cutoffSec=1800
- The endpoint may return GeoJSON or a zipped Shapefile.
- Your app should set header
Accept: application/json. As a fallback, include shpjs to parse zipped shapefiles to GeoJSON in-browser.
// include in browser:
/*
<script src="https://unpkg.com/shpjs@latest/dist/shp.min.js"></script>
*/
async function fetchIsochrone(base, params) {
const url = `${base}/isochrone?${params.toString()}`;
const r = await fetch(url, {
headers: { "Accept": "application/json", "Content-Type": "*/*" }
});
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
const ct = (r.headers.get("content-type") || "").toLowerCase();
if (ct.includes("json")) return r.json();
if (ct.includes("zip") || ct.includes("octet-stream")) {
const buf = await (await r.blob()).arrayBuffer();
const parsed = await shp(buf);
if (parsed && parsed.type === "FeatureCollection") return parsed;
if (parsed && typeof parsed === "object") {
const feats = Object.values(parsed)
.filter(v => v && v.type === "FeatureCollection")
.flatMap(fc => fc.features || []);
return { type: "FeatureCollection", features: feats };
}
throw new Error("Unrecognized shapefile contents");
}
throw new Error(`Unexpected content-type: ${ct}`);
}We bundle Caddy (single binary) to serve the web app and proxy /otp to OTP. This solves CORS and gives a same-origin URL.
:3000 {
root * ../web
file_server
@preflight {
method OPTIONS
path /otp/*
}
header @preflight Access-Control-Allow-Origin "*"
header @preflight Access-Control-Allow-Methods "GET, POST, OPTIONS"
header @preflight Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization"
respond @preflight 204
@otp path /otp/*
header @otp Access-Control-Allow-Origin "*"
header @otp Access-Control-Allow-Methods "GET, POST, OPTIONS"
header @otp Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization"
reverse_proxy 127.0.0.1:8080
}
const base = "http://localhost:3000/otp/routers/default";
- If OTP logs mention Kryo/Objenesis reflection errors, you are on Java 11/17 without module flags; use the included start scripts or Java 8.
- If the API returns a zip, your fallback parser will convert it to GeoJSON.
- If the browser shows CORS errors, ensure you started the proxy and your app is using http://localhost:3000 as base.
Transport for Cairo (TfC) — info@transportforcairo.com https://transportforcairo.com