summaryrefslogtreecommitdiff
path: root/map-server
diff options
context:
space:
mode:
authorBdale Garbee <bdale@gag.com>2018-10-08 17:52:22 -0600
committerBdale Garbee <bdale@gag.com>2018-10-08 17:52:22 -0600
commit2450e69d3afd3345a6e5f7ca133d929fbdcfdf53 (patch)
treec04aa34bdecbf909dbeea8854390364bf140e7ca /map-server
parent449ed72371f80f03dcc5dcf66af5c9465e7ed57e (diff)
parent5a86ff43a2b4a90dbddc1bba48019626e1b83e10 (diff)
Merge branch 'branch-1.8' into debian
Diffstat (limited to 'map-server')
-rw-r--r--map-server/Makefile.am1
-rw-r--r--map-server/altos-map/.gitignore1
-rw-r--r--map-server/altos-map/Makefile.am6
-rwxr-xr-xmap-server/altos-map/altos-map-fake5
-rw-r--r--map-server/altos-map/altos-map.c293
-rw-r--r--map-server/altos-mapd/.gitignore6
-rw-r--r--map-server/altos-mapd/AltosMapd.java235
-rw-r--r--map-server/altos-mapd/AltosMapdClient.java156
-rw-r--r--map-server/altos-mapd/AltosMapdPreferences.java85
-rw-r--r--map-server/altos-mapd/AltosMapdServer.java24
-rw-r--r--map-server/altos-mapd/Makefile.am73
-rw-r--r--map-server/altos-mapd/Manifest.txt2
-rw-r--r--map-server/altos-mapd/altos-mapd-default4
-rw-r--r--map-server/altos-mapd/altos-mapd.service14
-rw-r--r--map-server/altos-mapj/.gitignore6
-rw-r--r--map-server/altos-mapj/AltosMap.java182
-rw-r--r--map-server/altos-mapj/Makefile.am62
-rw-r--r--map-server/altos-mapj/Manifest.txt2
-rwxr-xr-xmap-server/altos-mapj/altos-mapj-fake6
19 files changed, 1163 insertions, 0 deletions
diff --git a/map-server/Makefile.am b/map-server/Makefile.am
new file mode 100644
index 00000000..39ff8014
--- /dev/null
+++ b/map-server/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=altos-mapd altos-map altos-mapj
diff --git a/map-server/altos-map/.gitignore b/map-server/altos-map/.gitignore
new file mode 100644
index 00000000..1158b56a
--- /dev/null
+++ b/map-server/altos-map/.gitignore
@@ -0,0 +1 @@
+altos-map
diff --git a/map-server/altos-map/Makefile.am b/map-server/altos-map/Makefile.am
new file mode 100644
index 00000000..6925957b
--- /dev/null
+++ b/map-server/altos-map/Makefile.am
@@ -0,0 +1,6 @@
+bin_PROGRAMS = altos-map
+
+altos_map_SOURCES = altos-map.c
+
+altos_map_LDADD = $(JANSSON_LIBS)
+altos_map_CFLAGS = $(JANSSON_CFLAGS) $(WARN_CFLAGS)
diff --git a/map-server/altos-map/altos-map-fake b/map-server/altos-map/altos-map-fake
new file mode 100755
index 00000000..1bf5f8e2
--- /dev/null
+++ b/map-server/altos-map/altos-map-fake
@@ -0,0 +1,5 @@
+#!/bin/sh
+# map-N43.799102,W120.586281-hybrid-20.jpg
+export QUERY_STRING="lat=43.799102&lon=-120.586281&zoom=20"
+export REMOTE_ADDR="127.0.0.1"
+./altos-map
diff --git a/map-server/altos-map/altos-map.c b/map-server/altos-map/altos-map.c
new file mode 100644
index 00000000..38701076
--- /dev/null
+++ b/map-server/altos-map/altos-map.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <jansson.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+
+#define ALTOS_MAP_PORT 16717
+
+#define MISSING 0x7fffffff
+
+#define ALTOS_MAP_PROTOCOL_VERSION "1.0.0"
+
+static char *
+reason_string(int code)
+{
+ switch (code) {
+ case 200:
+ return "OK";
+ case 400:
+ return "Bad Request";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 408:
+ return "Request Timeout";
+ default:
+ return "Failure";
+ }
+}
+
+static void
+write_status(int status)
+{
+ printf("Status: %d %s\n", status, reason_string(status));
+}
+
+static void
+write_type(char * type)
+{
+ printf("Content-Type: %s\n", type);
+}
+
+static void
+fail(int status, char *format, ...)
+{
+ va_list ap;
+
+ write_status(status);
+ write_type("text/html");
+ printf("\n");
+ printf("<html>\n");
+ printf("<head><title>Map Fetch Failure</title></head>\n");
+ printf("<body>\n");
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+ printf("</body>\n");
+ printf("</html>\n");
+ exit(1);
+}
+
+static char *
+getenv_copy(const char *name)
+{
+ const char *value = getenv(name);
+
+ if (!value)
+ return NULL;
+
+ return strdup(value);
+}
+
+static double
+parse_double(char *string)
+{
+ char *end;
+ double value;
+
+ value = strtod(string, &end);
+ if (*end)
+ fail(400, "Invalid double %s", string);
+ return value;
+}
+
+static int
+parse_int(char *string)
+{
+ char *end;
+ long int value;
+
+ value = strtol(string, &end, 10);
+ if (*end)
+ fail(400, "Invalid int %s", string);
+ if (value < INT_MIN || INT_MAX < value)
+ fail(400, "Int value out of range %ld", value);
+ return (int) value;
+}
+
+
+static int
+connect_service(void)
+{
+ struct sockaddr_in altos_map_addr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(ALTOS_MAP_PORT),
+ .sin_addr = {
+ .s_addr = htonl(INADDR_LOOPBACK),
+ },
+ };
+
+ int s = socket(AF_INET, SOCK_STREAM, 0);
+
+ if (s < 0)
+ return -1;
+
+ if (connect (s, (const struct sockaddr *) &altos_map_addr, sizeof (altos_map_addr)) < 0) {
+ close (s);
+ return -1;
+ }
+
+ return s;
+}
+
+int main(int argc, char **argv)
+{
+
+ char *query_string = getenv_copy("QUERY_STRING");
+
+ if (query_string == NULL)
+ fail(400, "%s", "Missing query string");
+
+ char *remote_addr = getenv_copy("REMOTE_ADDR");
+
+ if (remote_addr == NULL)
+ fail(400, "%s", "Missing remote address");
+
+ double lon = MISSING;
+ double lat = MISSING;
+ int zoom = MISSING;
+ char *version = NULL;
+
+ char *query, *query_save = NULL;
+ char *query_start = query_string;
+
+ while ((query = strtok_r(query_start, "&", &query_save)) != NULL) {
+ query_start = NULL;
+
+ char *token, *token_save = NULL;
+ char *token_start = query;
+
+ char *name = NULL;
+ char *value = NULL;
+
+ while ((token = strtok_r(token_start, "=", &token_save)) != NULL) {
+ token_start = NULL;
+ if (name == NULL)
+ name = token;
+ else if (value == NULL)
+ value = token;
+ else
+ break;
+ }
+
+ if (name && value) {
+ if (!strcmp(name, "lon"))
+ lon = parse_double(value);
+ else if (!strcmp(name, "lat"))
+ lat = parse_double(value);
+ else if (!strcmp(name, "zoom"))
+ zoom = parse_int(value);
+ else if (!strcmp(name, "version"))
+ version = value;
+ else
+ fail(400, "Extra query param \"%s\"", query);
+ }
+ }
+
+ if (version != NULL) {
+ printf("Content-Type: text/plain\n");
+ printf("\n");
+ printf("%s\n", ALTOS_MAP_PROTOCOL_VERSION);
+ return 0;
+ }
+ if (lon == MISSING)
+ fail(400, "Missing longitude");
+ if (lat == MISSING)
+ fail(400, "Missing latitude");
+ if (zoom == MISSING)
+ fail(400, "Missing zoom");
+
+ int s = -1;
+ int tries = 0;
+
+ while (tries < 10 && s < 0) {
+ s = connect_service();
+ if (s < 0) {
+ usleep(100 * 1000);
+ tries++;
+ }
+ }
+
+ if (s < 0)
+ fail(408, "Cannot connect AltOS map daemon");
+
+ FILE *sf = fdopen(s, "r+");
+
+ if (sf == NULL)
+ fail(400, "allocation failure");
+
+ json_t *request = json_pack("{s:f s:f s:i s:s}", "lat", lat, "lon", lon, "zoom", zoom, "remote_addr", remote_addr);
+
+ if (request == NULL)
+ fail(400, "Cannot create JSON request");
+
+ if (json_dumpf(request, sf, 0) < 0)
+ fail(400, "Cannot write JSON request");
+
+ fflush(sf);
+
+ json_error_t error;
+ json_t *reply = json_loadf(sf, 0, &error);
+
+ if (!reply)
+ fail(400, "Cannot read JSON reply");
+
+ int status;
+
+ if (json_unpack(reply, "{s:i}", "status", &status) < 0)
+ fail(400, "No status returned");
+
+ if (status != 200)
+ fail(status, "Bad cache status");
+
+ char *filename, *content_type;
+
+ if (json_unpack(reply, "{s:s s:s}", "filename", &filename, "content_type", &content_type) < 0)
+ fail(400, "JSON reply parse failure");
+
+ int fd = open(filename, O_RDONLY);
+
+ if (fd < 0)
+ fail(400, "%s: %s", filename, strerror(errno));
+
+ struct stat statb;
+
+ if (fstat(fd, &statb) < 0)
+ fail(400, "%s: %s", filename, strerror(errno));
+
+ printf("Content-Type: %s\n", content_type);
+ printf("Content-Length: %lu\n", (unsigned long) statb.st_size);
+ printf("\n");
+ fflush(stdout);
+
+ char buf[4096];
+ ssize_t bytes_read;
+
+ while ((bytes_read = read(fd, buf, sizeof (buf))) > 0) {
+ ssize_t total_write = 0;
+ while (total_write < bytes_read) {
+ ssize_t bytes_write = write(1, buf + total_write, bytes_read - total_write);
+ if (bytes_write <= 0)
+ return 1;
+ total_write += bytes_write;
+ }
+ }
+ if (bytes_read < 0)
+ return 1;
+ return 0;
+}
diff --git a/map-server/altos-mapd/.gitignore b/map-server/altos-mapd/.gitignore
new file mode 100644
index 00000000..5f5ce0ae
--- /dev/null
+++ b/map-server/altos-mapd/.gitignore
@@ -0,0 +1,6 @@
+*.stamp
+*.jar
+altos-mapd
+altos-mapd-jdb
+altos-mapd-test
+classes
diff --git a/map-server/altos-mapd/AltosMapd.java b/map-server/altos-mapd/AltosMapd.java
new file mode 100644
index 00000000..29528541
--- /dev/null
+++ b/map-server/altos-mapd/AltosMapd.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+package altosmapd;
+
+import java.net.*;
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.altusmetrum.altoslib_13.*;
+
+public class AltosMapd implements AltosLaunchSiteListener {
+
+ public static int port = 16717;
+
+ public final static int maptype = AltosMap.maptype_hybrid;
+
+ public final static int px_size = 512;
+
+ public final static int scale = 1;
+
+ public static int max_zoom = 17;
+
+ public static double valid_radius = 17000; /* 17km */
+
+ public String map_dir = null;
+ public String launch_sites_file = null;
+ public String key_file = null;
+
+ public void usage() {
+ System.out.printf("usage: altos-mapd [--mapdir <map-directory] [--launch-sites <launch-sites-file>]\n" +
+ " [--radius <valid-radius-m> [--port <port>] [--key <key-file>]\n" +
+ " [--max-zoom <max-zoom-level>\n");
+ System.exit(1);
+ }
+
+ private static Semaphore launch_sites_ready;
+
+ private static List<AltosLaunchSite> launch_sites;
+
+ public void notify_launch_sites(List<AltosLaunchSite> sites) {
+ synchronized (launch_sites_ready) {
+ if (sites != null) {
+ launch_sites = sites;
+ launch_sites_ready.release();
+ }
+ }
+ }
+
+ private static boolean west_of(double a, double b) {
+ double diff = b - a;
+
+ while (diff >= 360.0)
+ diff -= 360.0;
+ while (diff <= -360.0)
+ diff += 360.0;
+
+ return diff >= 0;
+ }
+
+ public static boolean check_lat_lon(double lat, double lon, int zoom) {
+
+ if (zoom > max_zoom)
+ return false;
+
+ AltosMapTransform transform = new AltosMapTransform(px_size, px_size, zoom, new AltosLatLon(lat, lon));
+
+ AltosLatLon upper_left = transform.screen_lat_lon(new AltosPointInt(0, 0));
+ AltosLatLon lower_right = transform.screen_lat_lon(new AltosPointInt(px_size, px_size));
+
+ synchronized (launch_sites_ready) {
+ if (launch_sites == null) {
+ try {
+ launch_sites_ready.acquire();
+ } catch (InterruptedException ie) {
+ return false;
+ }
+ }
+ }
+ if (launch_sites == null) {
+ System.out.printf("No launch site data available, refusing all requests\n");
+ return false;
+ }
+
+ for (AltosLaunchSite site : launch_sites) {
+
+ /* Figure out which point in the tile to
+ * measure to the site That's one of the edges
+ * or the site location depend on where the
+ * site is in relation to the tile
+ */
+
+ double check_lon;
+
+ if (west_of(site.longitude, upper_left.lon))
+ check_lon = upper_left.lon;
+ else if (west_of(lower_right.lon, site.longitude))
+ check_lon = lower_right.lon;
+ else
+ check_lon = site.longitude;
+
+ double check_lat;
+
+ if (site.latitude < lower_right.lat)
+ check_lat = lower_right.lat;
+ else if (upper_left.lat < site.latitude)
+ check_lat = upper_left.lat;
+ else
+ check_lat = site.latitude;
+
+ AltosGreatCircle gc = new AltosGreatCircle(site.latitude, site.longitude,
+ check_lat, check_lon);
+
+ if (gc.distance <= valid_radius)
+ return true;
+ }
+
+ return false;
+ }
+
+ AltosMapdServer server;
+
+ public void process(String[] args) {
+
+ AltosPreferences.init(new AltosMapdPreferences());
+
+ int skip = 1;
+ for (int i = 0; i < args.length; i += skip) {
+ skip = 1;
+ if (args[i].equals("--mapdir") && i < args.length-1) {
+ map_dir = args[i+1];
+ skip = 2;
+ } else if (args[i].equals("--launch-sites") && i < args.length-1) {
+ launch_sites_file = args[i+1];
+ skip = 2;
+ } else if (args[i].equals("--radius") && i < args.length-1) {
+ try {
+ valid_radius = AltosParse.parse_double_locale(args[i+1]);
+ } catch (ParseException pe) {
+ usage();
+ }
+ skip = 2;
+ } else if (args[i].equals("--port") && i < args.length-1) {
+ try {
+ port = AltosParse.parse_int(args[i+1]);
+ } catch (ParseException pe) {
+ usage();
+ }
+ skip = 2;
+ } else if (args[i].equals("--key") && i < args.length-1) {
+ key_file = args[i+1];
+ skip = 2;
+ } else if (args[i].equals("--max-zoom") && i < args.length-1) {
+ try {
+ max_zoom = AltosParse.parse_int(args[i+1]);
+ } catch (ParseException pe) {
+ usage();
+ }
+ skip = 2;
+ } else {
+ usage();
+ }
+ }
+
+ if (map_dir == null)
+ usage();
+
+ if (key_file != null) {
+ try {
+ BufferedReader key_reader = new BufferedReader(new FileReader(key_file));
+
+ String line = key_reader.readLine();
+ if (line == null || line.length() != 39) {
+ System.out.printf("%s: invalid contents %d \"%s\"\n",
+ key_file, line.length(), line);
+ usage();
+ }
+ key_reader.close();
+ AltosMapStore.google_maps_api_key = line;
+ } catch (Exception e) {
+ System.out.printf("%s: %s\n", key_file, e.toString());
+ usage();
+ }
+ }
+
+ AltosPreferences.mapdir = new File(map_dir);
+
+ if (launch_sites_file != null)
+ AltosLaunchSites.launch_sites_url = "file://" + launch_sites_file;
+
+ launch_sites_ready = new Semaphore(0);
+
+ new AltosLaunchSites(this);
+
+ try {
+ server = new AltosMapdServer(port);
+ } catch (IOException ie) {
+ System.out.printf("Cannot bind to port %d: %s\n", port, ie.toString());
+ usage();
+ }
+
+ for (;;) {
+ try {
+ Socket client = server.accept();
+ if (client == null) {
+ System.out.printf("accept failed\n");
+ continue;
+ }
+ new AltosMapdClient(client);
+ } catch (Exception e) {
+ System.out.printf("Exception %s\n", e.toString());
+ }
+ }
+ }
+
+ public void AltosMapd() {
+ }
+
+ public static void main(final String[] args) {
+ new AltosMapd().process(args);
+ }
+}
diff --git a/map-server/altos-mapd/AltosMapdClient.java b/map-server/altos-mapd/AltosMapdClient.java
new file mode 100644
index 00000000..6c95da8f
--- /dev/null
+++ b/map-server/altos-mapd/AltosMapdClient.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+package altosmapd;
+
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.io.*;
+
+import org.altusmetrum.altoslib_13.*;
+
+public class AltosMapdClient extends Thread implements AltosMapStoreListener {
+ private Socket socket;
+ private AltosJson request;
+ private AltosJson reply;
+
+ private int http_status;
+
+ private void set_status(int status) {
+ http_status = status;
+ reply.put("status", status);
+ }
+
+ private void set_filename(String filename) {
+ reply.put("filename", filename);
+
+ }
+
+ private void set_content_type(String content_type) {
+ reply.put("content_type", content_type);
+ }
+
+ private String content_type(File file) {
+ String content_type = "application/octet-stream";
+ String basename = file.getName();
+ if (basename.endsWith(".jpg"))
+ content_type = "image/jpeg";
+ if (basename.endsWith(".png"))
+ content_type = "image/png";
+ return content_type;
+ }
+
+ private void set_file(File file) {
+ set_filename(file.getAbsolutePath());
+ set_content_type(content_type(file));
+ }
+
+ private Semaphore store_ready;
+
+ public void notify_store(AltosMapStore map_store, int status) {
+ if (status != AltosMapTile.fetching)
+ store_ready.release();
+ }
+
+ public void run() {
+ reply = new AltosJson();
+ try {
+ request = AltosJson.fromInputStream(socket.getInputStream());
+
+ if (request == null) {
+ set_status(400);
+ System.out.printf("client failed %d\n", http_status);
+ } else {
+
+ double lat = request.get_double("lat", AltosLib.MISSING);
+ double lon = request.get_double("lon", AltosLib.MISSING);
+ int zoom = request.get_int("zoom", AltosLib.MISSING);
+ String addr = request.get_string("remote_addr", null);
+
+ if (lat == AltosLib.MISSING ||
+ lon == AltosLib.MISSING ||
+ zoom == AltosLib.MISSING ||
+ addr == null)
+ {
+ set_status(400);
+ } else if (!AltosMapd.check_lat_lon(lat, lon, zoom)) {
+ set_status(403); /* Forbidden */
+ } else {
+
+ store_ready = new Semaphore(0);
+
+ AltosMapStore map_store = AltosMapStore.get(new AltosLatLon(lat, lon),
+ zoom,
+ AltosMapd.maptype,
+ AltosMapd.px_size,
+ AltosMapd.scale);
+ int status;
+
+ if (map_store == null) {
+ status = AltosMapTile.failed;
+ } else {
+ map_store.add_listener(this);
+
+ try {
+ store_ready.acquire();
+ } catch (Exception ie) {
+ }
+
+ status = map_store.status();
+ }
+
+ if (status == AltosMapTile.fetched || status == AltosMapTile.loaded) {
+ set_status(200);
+ set_file(map_store.file);
+ } else if (status == AltosMapTile.failed) {
+ set_status(404);
+ } else if (status == AltosMapTile.fetching) {
+ set_status(408);
+ } else if (status == AltosMapTile.bad_request) {
+ set_status(400);
+ } else if (status == AltosMapTile.forbidden) {
+ set_status(403);
+ } else {
+ set_status(400);
+ }
+ }
+ System.out.printf("%s: %.6f %.6f %d status %d\n",
+ addr, lat, lon, zoom, http_status);
+ }
+ } catch (Exception e) {
+ System.out.printf("client exception %s\n", e.toString());
+ e.printStackTrace(System.out);
+ set_status(400);
+
+ } finally {
+ try {
+ Writer writer = new PrintWriter(socket.getOutputStream());
+ reply.write(writer);
+ writer.write('\n');
+ writer.flush();
+ } catch (IOException ie) {
+ }
+ try {
+ socket.close();
+ } catch (IOException ie) {
+ }
+ }
+ }
+
+ public AltosMapdClient(Socket socket) {
+ this.socket = socket;
+ start();
+ }
+}
diff --git a/map-server/altos-mapd/AltosMapdPreferences.java b/map-server/altos-mapd/AltosMapdPreferences.java
new file mode 100644
index 00000000..fcfe3261
--- /dev/null
+++ b/map-server/altos-mapd/AltosMapdPreferences.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+package altosmapd;
+
+import java.io.*;
+
+import org.altusmetrum.altoslib_13.*;
+
+public class AltosMapdPreferences extends AltosPreferencesBackend {
+
+ public String getString(String key, String def) {
+ return def;
+ }
+ public void putString(String key, String value) {
+ }
+
+ public int getInt(String key, int def) {
+ return def;
+ }
+
+ public void putInt(String key, int value) {
+ }
+
+ public double getDouble(String key, double def) {
+ return def;
+ }
+
+ public void putDouble(String key, double value) {
+ }
+
+ public boolean getBoolean(String key, boolean def) {
+ return def;
+ }
+
+ public void putBoolean(String key, boolean value) {
+ }
+
+ public byte[] getBytes(String key, byte[] def) {
+ return def;
+ }
+
+ public void putBytes(String key, byte[] value) {
+ }
+
+ public boolean nodeExists(String key) {
+ return false;
+ }
+
+ public AltosPreferencesBackend node(String key) {
+ return this;
+ }
+
+ public String[] keys() {
+ return null;
+ }
+
+ public void remove(String key) {
+ }
+
+ public void flush() {
+ }
+
+ public File homeDirectory() {
+ return new File (".");
+ }
+
+ public void debug(String format, Object ... arguments) {
+ System.out.printf(format, arguments);
+ }
+
+ public AltosMapdPreferences() {
+ }
+}
diff --git a/map-server/altos-mapd/AltosMapdServer.java b/map-server/altos-mapd/AltosMapdServer.java
new file mode 100644
index 00000000..db7f7dc1
--- /dev/null
+++ b/map-server/altos-mapd/AltosMapdServer.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+package altosmapd;
+
+import java.net.*;
+import java.io.*;
+
+public class AltosMapdServer extends ServerSocket {
+ public AltosMapdServer(int port) throws IOException {
+ super(port, 256, InetAddress.getLoopbackAddress());
+ }
+}
diff --git a/map-server/altos-mapd/Makefile.am b/map-server/altos-mapd/Makefile.am
new file mode 100644
index 00000000..39f67536
--- /dev/null
+++ b/map-server/altos-mapd/Makefile.am
@@ -0,0 +1,73 @@
+JAVAROOT=classes
+AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6
+
+altoslibdir=$(libdir)/altos
+
+CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH="$(JAVAROOT):../../altoslib/*"
+
+bin_SCRIPTS=altos-mapd
+
+altosmapd_JAVA = \
+ AltosMapd.java \
+ AltosMapdServer.java \
+ AltosMapdClient.java \
+ AltosMapdPreferences.java
+
+ALTOSLIB_CLASS=\
+ altoslib_$(ALTOSLIB_VERSION).jar
+
+JAR=altosmapd.jar
+
+FATJAR=altosmapd-fat.jar
+
+all-local: classes/altosmapd $(JAR) altos-mapd altos-mapd-test altos-mapd-jdb
+
+defaultsdir=$(sysconfdir)/default
+
+defaults_DATA=altos-mapd-default
+
+systemddir=$(libdir)/systemd/system
+
+systemd_DATA=altos-mapd.service
+
+install-altosmapdJAVA: altosmapd.jar
+ @$(NORMAL_INSTALL)
+ test -z "$(altosmapddir)" || $(MKDIR_P) "$(DESTDIR)$(altosmapddir)"
+ echo " $(INSTALL_DATA)" "$<" "'$(DESTDIR)$(altosmapddir)/altosdmap.jar'"; \
+ $(INSTALL_DATA) "$<" "$(DESTDIR)$(altosmapddir)"
+
+classes/altosmapd:
+ mkdir -p classes/altosmapd
+
+$(JAR): classaltosmapd.stamp Manifest.txt $(ALTOSLIB_CLASS)
+ jar cfm $@ Manifest.txt \
+ -C classes altosmapd
+
+altosmapddir=$(datadir)/java
+
+$(FATJAR): classaltosmapd.stamp Manifest-fat.txt $(ALTOSLIB_CLASS)
+ jar cfm $@ Manifest-fat.txt \
+ -C classes altosmapd
+
+altos-mapd: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec java -Djava.library.path="$(altoslibdir)" -jar "$(altosmapddir)/altosmapd.jar" "$$@"' >> $@
+ chmod +x $@
+
+altos-mapd-test: Makefile
+ echo '#!/bin/sh' > $@
+ echo 'dir="$$(dirname $$0)"' >> $@
+ echo 'cd "$$dir"' >> $@
+ echo 'altosmapd="$$(pwd -P)"' >> $@
+ echo 'exec java -jar "$$altosmapd/altosmapd.jar" "$$@"' >> $@
+ chmod +x $@
+
+altos-mapd-jdb: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec jdb altosmapd/AltosMapd "$$@"' >> $@
+ chmod +x $@
+
+$(ALTOSLIB_CLASS):
+ -rm -f "$@"
+ $(LN_S) ../../altoslib/"$@" .
+
diff --git a/map-server/altos-mapd/Manifest.txt b/map-server/altos-mapd/Manifest.txt
new file mode 100644
index 00000000..42c0313b
--- /dev/null
+++ b/map-server/altos-mapd/Manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: altosmapd.AltosMapd
+Class-Path: altoslib_13.jar
diff --git a/map-server/altos-mapd/altos-mapd-default b/map-server/altos-mapd/altos-mapd-default
new file mode 100644
index 00000000..1611f0b6
--- /dev/null
+++ b/map-server/altos-mapd/altos-mapd-default
@@ -0,0 +1,4 @@
+MAPKEY=/home/altos-mapd/google-maps-api-key
+MAPDIR=/home/altos-mapd/maps
+LAUNCHSITES=/var/www/html/launch-sites.txt
+MAXZOOM=17
diff --git a/map-server/altos-mapd/altos-mapd.service b/map-server/altos-mapd/altos-mapd.service
new file mode 100644
index 00000000..ba263c2a
--- /dev/null
+++ b/map-server/altos-mapd/altos-mapd.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=AltOS Map Cache
+Requires=network-online.target
+After=network-online.target
+
+[Service]
+Type=simple
+User=altos-mapd
+Restart=always
+EnvironmentFile=/etc/default/altos-mapd-default
+ExecStart=/usr/bin/altos-mapd --key $MAPKEY --mapdir $MAPDIR --launch-sites $LAUNCHSITES --max-zoom $MAXZOOM
+
+[Install]
+WantedBy=multi-user.target
diff --git a/map-server/altos-mapj/.gitignore b/map-server/altos-mapj/.gitignore
new file mode 100644
index 00000000..c5d593e6
--- /dev/null
+++ b/map-server/altos-mapj/.gitignore
@@ -0,0 +1,6 @@
+altos-mapj
+altos-mapj-jdb
+altos-mapj-test
+*.jar
+*.stamp
+classes
diff --git a/map-server/altos-mapj/AltosMap.java b/map-server/altos-mapj/AltosMap.java
new file mode 100644
index 00000000..ad0a4f5f
--- /dev/null
+++ b/map-server/altos-mapj/AltosMap.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright © 2018 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+package altosmap;
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+
+import org.altusmetrum.altoslib_13.*;
+
+public class AltosMap {
+
+ public final static int port = 16717;
+
+ public final static String protocol_version = "1.0.0";
+
+ String query_string;
+ String remote_addr;
+
+ public String reason_string(int code) {
+ switch (code) {
+ case 200:
+ return "OK";
+ case 400:
+ return "Bad Request";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 408:
+ return "Request Timeout";
+ default:
+ return "Failure";
+ }
+ }
+
+ public void write_status(int status) {
+ System.out.printf("Status: %d %s\n", status, reason_string(status));
+ }
+
+ public void write_type(String type) {
+ System.out.printf("Content-Type: %s\n", type);
+ }
+
+ public void fail(int status, String reason) {
+ write_status(status);
+ write_type("text/html");
+ System.out.printf("\n");
+ System.out.printf("<html>\n");
+ System.out.printf("<head><title>Map Fetch Failure</title></head>\n");
+ System.out.printf("<body>%s</body>\n", reason);
+ System.out.printf("</html>\n");
+ System.exit(1);
+ }
+
+ public void process() {
+ query_string = System.getenv("QUERY_STRING");
+
+ if (query_string == null)
+ fail(400, "Missing query string");
+
+ remote_addr = System.getenv("REMOTE_ADDR");
+
+ if (remote_addr == null)
+ fail(400, "Missing remote address");
+
+ String[] queries = query_string.split("&");
+
+ double lon = AltosLib.MISSING;
+ double lat = AltosLib.MISSING;
+ int zoom = AltosLib.MISSING;
+ String version = null;
+
+ try {
+ for (String query : queries) {
+ String[] q = query.split("=");
+ if (q.length >= 2) {
+ String name = q[0];
+ String value = q[1];
+ if (name.equals("lon"))
+ lon = AltosParse.parse_double_net(value);
+ else if (name.equals("lat"))
+ lat = AltosParse.parse_double_net(value);
+ else if (name.equals("zoom"))
+ zoom = AltosParse.parse_int(value);
+ else if (name.equals("version"))
+ version = value;
+ else
+ fail(400, String.format("Extra query param \"%s\"", query));
+ }
+ }
+ } catch (ParseException pe) {
+ fail(400, String.format("Invalid query: %s", pe.toString()));
+ }
+
+ if (version != null) {
+ System.out.printf("Content-Type: text/plain\n");
+ System.out.printf("\n");
+ System.out.printf("%s\n", protocol_version);
+ } else {
+ if (lon == AltosLib.MISSING)
+ fail(400, "Missing longitude");
+ if (lat == AltosLib.MISSING)
+ fail(400, "Missing latitude");
+ if (zoom == AltosLib.MISSING)
+ fail(400, "Missing zoom");
+
+ try {
+ Socket socket = null;
+ int tries = 0;
+
+ while (tries < 10 && socket == null) {
+ try {
+ socket = new Socket(InetAddress.getLoopbackAddress(), port);
+ } catch (IOException ie) {
+ Thread.sleep(100);
+ tries++;
+ }
+ }
+
+ AltosJson request = new AltosJson();
+
+ request.put("lat", lat);
+ request.put("lon", lon);
+ request.put("zoom", zoom);
+ request.put("remote_addr", remote_addr);
+
+ Writer writer = new PrintWriter(socket.getOutputStream());
+ request.write(writer);
+ writer.flush();
+
+ AltosJson reply = AltosJson.fromInputStream(socket.getInputStream());
+
+ int status = reply.get_int("status", 400);
+
+ if (status != 200)
+ fail(status, "Bad cache status");
+
+ String filename = reply.get_string("filename", null);
+ try {
+ File file = new File(filename);
+ long length = file.length();
+ FileInputStream in = new FileInputStream(file);
+ String content_type = reply.get_string("content_type", null);
+ System.out.printf("Content-Type: %s\n", content_type);
+ System.out.printf("Content-Length: %d\n", file.length());
+ System.out.printf("\n");
+ byte[] buf = new byte[4096];
+ int bytes_read;
+ while ((bytes_read = in.read(buf)) > 0)
+ System.out.write(buf);
+ } catch (IOException ie) {
+ fail(404, String.format("IO Exception: %s", ie.toString()));
+ }
+ } catch (Exception e) {
+ fail(404, String.format("Exception %s", e.toString()));
+ }
+ }
+ }
+
+ public AltosMap() {
+ }
+
+ public static void main(final String[] args) {
+
+ new AltosMap().process();
+
+ }
+}
diff --git a/map-server/altos-mapj/Makefile.am b/map-server/altos-mapj/Makefile.am
new file mode 100644
index 00000000..f447adb6
--- /dev/null
+++ b/map-server/altos-mapj/Makefile.am
@@ -0,0 +1,62 @@
+JAVAROOT=classes
+AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6
+
+altoslibdir=$(libdir)/altos
+
+CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH="$(JAVAROOT):../../altoslib/*"
+
+bin_SCRIPTS=altos-mapj
+
+altosmap_JAVA = \
+ AltosMap.java
+
+ALTOSLIB_CLASS=\
+ altoslib_$(ALTOSLIB_VERSION).jar
+
+JAR=altosmap.jar
+
+FATJAR=altosmap-fat.jar
+
+all-local: classes/altosmap $(JAR) altos-mapj altos-mapj-test altos-mapj-jdb
+
+install-altosmapJAVA: altosmap.jar
+ @$(NORMAL_INSTALL)
+ test -z "$(altosmapdir)" || $(MKDIR_P) "$(DESTDIR)$(altosmapdir)"
+ echo " $(INSTALL_DATA)" "$<" "'$(DESTDIR)$(altosmapdir)/altosmap.jar'"; \
+ $(INSTALL_DATA) "$<" "$(DESTDIR)$(altosmapdir)"
+
+classes/altosmap:
+ mkdir -p classes/altosmap
+
+$(JAR): classaltosmap.stamp Manifest.txt $(ALTOSLIB_CLASS)
+ jar cfm $@ Manifest.txt \
+ -C classes altosmap
+
+altosmapdir=$(datadir)/java
+
+$(FATJAR): classaltosmap.stamp Manifest-fat.txt $(ALTOSLIB_CLASS)
+ jar cfm $@ Manifest-fat.txt \
+ -C classes altosmap
+
+altos-mapj: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec java -Djava.library.path="$(altoslibdir)" -jar "$(altosmapdir)/altosmap.jar" "$$@"' >> $@
+ chmod +x $@
+
+altos-mapj-test: Makefile
+ echo '#!/bin/sh' > $@
+ echo 'dir="$$(dirname $$0)"' >> $@
+ echo 'cd "$$dir"' >> $@
+ echo 'altosmap="$$(pwd -P)"' >> $@
+ echo 'exec java -jar "$$altosmap/altosmap.jar" "$$@"' >> $@
+ chmod +x $@
+
+altos-mapj-jdb: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec jdb altosmap/AltosMap "$$@"' >> $@
+ chmod +x $@
+
+$(ALTOSLIB_CLASS):
+ -rm -f "$@"
+ $(LN_S) ../../altoslib/"$@" .
+
diff --git a/map-server/altos-mapj/Manifest.txt b/map-server/altos-mapj/Manifest.txt
new file mode 100644
index 00000000..1a285b40
--- /dev/null
+++ b/map-server/altos-mapj/Manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: altosmap.AltosMap
+Class-Path: altoslib_13.jar
diff --git a/map-server/altos-mapj/altos-mapj-fake b/map-server/altos-mapj/altos-mapj-fake
new file mode 100755
index 00000000..f600fec7
--- /dev/null
+++ b/map-server/altos-mapj/altos-mapj-fake
@@ -0,0 +1,6 @@
+#!/bin/sh
+# map-N43.799102,W120.586281-hybrid-20.jpg
+export QUERY_STRING="lat=43.799102&lon=-120.586281&zoom=20"
+export REMOTE_ADDR="127.0.0.1"
+./altos-mapj-test
+