{
  "openapi": "3.1.1",
  "info": {
    "title": "Cartograph API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://cartograph.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "cartograph-map-poster",
                "cartograph-route-art",
                "cartograph-meet-halfway",
                "cartograph-true-size"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/cartograph-map-poster/calculate": {
      "post": {
        "operationId": "calculateCartographMapPoster",
        "summary": "Turn an OpenStreetMap extract into a print-ready city map poster as SVG and vector PDF.",
        "description": "Draws a city map poster from your own OpenStreetMap extract (.osm XML, .osm.pbf or GeoJSON with OSM tags): roads by class, paths, railways, water, parks and woods, optional buildings, in one of six original colour styles, on A4–A2, 30 × 40, 50 × 70 or square paper, with a rectangular or round frame, title, subtitle, coordinates and the OpenStreetMap attribution. Returns a true-size SVG and a vector PDF. It is the poster engine of the Cartograph app.\n\nMethodology:\n- The extract is read locally: OSM XML (API 0.6), OSM PBF (zlib blocks, dense nodes, ways, multipolygon relations) or GeoJSON with OSM tags as properties. Nothing is downloaded from a map server.\n- Tags become layers: highway=* by class (motorway/trunk, primary, secondary/tertiary, streets, service/track, paths), railway=rail/light_rail/tram, waterway=river/canal/stream, natural=water and reservoirs (also multipolygons), parks/meadows/cemeteries and woods, building=*. Tunnels are left out.\n- Projection: Web Mercator around the centre, scaled so that the chosen half-width on the ground spans half of the frame. Every feature is clipped to the frame (Cyrus–Beck for lines, Sutherland–Hodgman for areas) and thinned to 0.04 mm, so the files carry only what is visible.\n- Line widths depend on the road class, the paper size, the area (wider areas get thinner lines) and the factor. Text uses Helvetica in the PDF; labels in other scripts are embedded as Noto subsets (Arabic shaping and Devanagari included).\n- Every poster carries “© OpenStreetMap contributors, ODbL”. Posters are Produced Works under the ODbL; the share-alike terms apply to databases, not to the printed map.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "cartograph_map_poster",
        "x-owner-app": "cartograph",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CartographMapPosterInput"
              },
              "examples": {
                "example1": {
                  "summary": "Vaduz on A3 paper (built-in OpenStreetMap sample)",
                  "value": {
                    "center": "47.1393, 9.5215",
                    "radiusM": 1200,
                    "paper": "a3",
                    "style": "paper",
                    "title": "Vaduz",
                    "subtitle": "Liechtenstein"
                  }
                },
                "example2": {
                  "summary": "Round midnight poster with buildings, 30 × 30 cm",
                  "value": {
                    "center": "47.1393, 9.5215",
                    "radiusM": 1000,
                    "paper": "square",
                    "frame": "circle",
                    "style": "midnight",
                    "buildings": true,
                    "title": "Vaduz",
                    "subtitle": "Liechtenstein"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartographMapPosterResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/cartograph-route-art/calculate": {
      "post": {
        "operationId": "calculateCartographRouteArt",
        "summary": "Snap a heart, star or other shape to the streets of your OSM extract and get a GPX route.",
        "description": "Places a shape (heart, star, circle, square, triangle, arrow or zigzag) at a point, size and rotation, samples it into waypoints, snaps them to the walkable or rideable roads of your own OpenStreetMap extract and joins them with shortest paths that stay close to the shape. Returns the route as GPX with its length and how closely it follows the drawing. Routing runs on the extract itself — no routing service is used. It is the GPS-art engine of the Cartograph app.\n\nMethodology:\n- The shape is placed with geodesics: every outline point keeps its direction and distance from the centre (Karney’s direct problem on WGS84), so the size is true on the ground.\n- The road graph is built from the extract: highway ways usable on foot or by bike (motorways and trunk roads, access=no/private and explicit foot=no/bicycle=no excluded; bikes avoid steps and footways unless allowed and follow one-way streets). Junctions are shared coordinates; edge lengths are great-circle distances.\n- The outline is sampled into waypoints; each is snapped to the nearest graph node within twice the spacing (dead ends avoided where possible). Consecutive waypoints are joined by A* shortest paths whose cost adds “stay-on-shape weight × distance from the shape segment ÷ spacing” per metre, so routes prefer streets that hug the drawing. Out-and-back spurs are removed.\n- Quality: mean distance of the route from the shape, the largest gap in either direction (Hausdorff-style) and the share of the shape that has the route within 25 m — measured every 10 m in a local equirectangular frame.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "cartograph_route_art",
        "x-owner-app": "cartograph",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CartographRouteArtInput"
              },
              "examples": {
                "example1": {
                  "summary": "Heart run in Vaduz (built-in sample)",
                  "value": {
                    "shape": "heart",
                    "center": "47.145, 9.517",
                    "widthM": 1000,
                    "profile": "foot"
                  }
                },
                "example2": {
                  "summary": "Star ride by bike",
                  "value": {
                    "shape": "star",
                    "center": "47.142, 9.52",
                    "widthM": 800,
                    "profile": "bike"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartographRouteArtResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/cartograph-meet-halfway/calculate": {
      "post": {
        "operationId": "calculateCartographMeetHalfway",
        "summary": "Find the fairest meeting point for 2–12 people: geodesic midpoint, minimax and least total distance.",
        "description": "Takes two to twelve places (any coordinate format) and finds the geodesic midpoint, the fairest point (the shortest longest trip, i.e. the centre of the smallest circle around everyone on the globe) and the point with the least total distance, with every person’s straight-line distance. With an OpenStreetMap extract it also finds the fairest meeting place by road for walking or cycling. It is the meet-halfway engine of the Cartograph app.\n\nMethodology:\n- All straight-line distances are geodesics on the WGS84 ellipsoid (Karney’s algorithm), not travel times.\n- Midpoint (two people): the point half-way along the geodesic between them.\n- Fairest point: minimises the longest distance anyone travels — the centre of the smallest spherical cap that contains everyone. Found by the Bădoiu–Clarkson iteration (step towards the farthest person by d/(i+1), 400 steps along geodesics) and refined by a compass search down to 1 cm.\n- Least total distance: the geometric median, by Weiszfeld iterations in the azimuthal-equidistant plane of the current estimate. Centre of gravity: the normalised mean of the 3-D unit vectors.\n- By road (optional): each person is snapped to the nearest road node within 1 km, Dijkstra runs from everyone over the extract’s road graph, and the node with the smallest longest road distance (ties: smallest total) is chosen.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "cartograph_meet_halfway",
        "x-owner-app": "cartograph",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CartographMeetHalfwayInput"
              },
              "examples": {
                "example1": {
                  "summary": "Berlin, Hamburg and Munich (straight line)",
                  "value": {
                    "people": "Berlin: 52.5163, 13.3777\nHamburg: 53.5503, 9.9920\nMunich: 48.1374, 11.5755"
                  }
                },
                "example2": {
                  "summary": "Two friends in Vaduz and Schaan, also by road (built-in sample)",
                  "value": {
                    "people": "Anna: 47.1355, 9.5195\nBen: 47.1505, 9.5135",
                    "useRoads": true,
                    "profile": "foot"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartographMeetHalfwayResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/cartograph-true-size/calculate": {
      "post": {
        "operationId": "calculateCartographTrueSize",
        "summary": "Move a country to another latitude and see its true size against the Mercator map.",
        "description": "Moves a country outline (Natural Earth admin-0) to any place on the globe while keeping its true shape and area, and shows how big it looks on a Web-Mercator map at home and at the new place, next to its real geodesic area and an optional second country. It is the true-size engine of the Cartograph app.\n\nMethodology:\n- Outlines: Natural Earth 1:110m admin-0 countries (public domain), the same file as the Geo Data Visualizer; small countries and islands below that scale are missing.\n- Moving: every vertex keeps its geodesic distance and azimuth from the anchor (Karney’s inverse problem at home, direct problem at the new place, WGS84). On a sphere this is an exact rotation; on the ellipsoid the true area changes by well under 1 % — both areas are reported.\n- True area: geodesic polygon area on WGS84 (outer rings minus holes). Apparent area: planar area of the outline in Web Mercator at equator scale, which grows with sec²(latitude) — 4 × at 60°.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "cartograph_true_size",
        "x-owner-app": "cartograph",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CartographTrueSizeInput"
              },
              "examples": {
                "example1": {
                  "summary": "Greenland moved to the equator, compared with DR Congo",
                  "value": {
                    "country": "GRL",
                    "target": "2, 20",
                    "compare": "COD"
                  }
                },
                "example2": {
                  "summary": "Brazil laid over Europe",
                  "value": {
                    "country": "Brazil",
                    "target": "51, 12"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartographTrueSizeResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "CartographMapPosterInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "OpenStreetMap extract (.osm, .osm.pbf or GeoJSON)",
            "description": "Up to 25 MB. Leave empty to use the built-in Vaduz sample. Download extracts from your own OpenStreetMap export, Geofabrik or BBBike; Cartograph never fetches map data itself. Upload as {filename, contentType?, contentBase64}. Max 24414 KiB. Accepts .osm,.pbf,.geojson,.json,application/xml,text/xml,application/geo+json,application/json,application/octet-stream.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "center": {
            "title": "Poster centre",
            "description": "Any coordinate format (decimal degrees, DMS, UTM, MGRS, plus code). Empty: the middle of the extract.",
            "type": "string",
            "default": ""
          },
          "radiusM": {
            "title": "Area half-width",
            "description": "Ground distance from the centre to the left and right edges of the map frame.",
            "type": "number",
            "minimum": 100,
            "maximum": 25000,
            "default": 1200,
            "x-unit": "m"
          },
          "paper": {
            "title": "Paper",
            "type": "string",
            "enum": [
              "a4",
              "a3",
              "a2",
              "30x40",
              "50x70",
              "letter",
              "square"
            ],
            "default": "a3"
          },
          "orientation": {
            "title": "Orientation",
            "type": "string",
            "enum": [
              "portrait",
              "landscape"
            ],
            "default": "portrait"
          },
          "frame": {
            "title": "Map frame",
            "type": "string",
            "enum": [
              "rect",
              "circle"
            ],
            "default": "rect"
          },
          "style": {
            "title": "Colour style",
            "type": "string",
            "enum": [
              "paper",
              "midnight",
              "blueprint",
              "terracotta",
              "mono",
              "forest"
            ],
            "default": "paper"
          },
          "paths": {
            "title": "Draw footpaths and cycleways",
            "type": "boolean",
            "default": true
          },
          "buildings": {
            "title": "Draw buildings",
            "type": "boolean",
            "default": false
          },
          "lineScale": {
            "title": "Line width factor",
            "type": "number",
            "minimum": 0.4,
            "maximum": 3,
            "default": 1
          },
          "title": {
            "title": "Title",
            "description": "Empty: the town name found in the extract.",
            "type": "string",
            "maxLength": 80,
            "default": ""
          },
          "subtitle": {
            "title": "Subtitle",
            "type": "string",
            "maxLength": 120,
            "default": ""
          },
          "showCoords": {
            "title": "Print the centre coordinates",
            "type": "boolean",
            "default": true
          }
        },
        "required": []
      },
      "CartographMapPosterResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "title",
              "paper",
              "orientation",
              "widthMm",
              "heightMm",
              "center",
              "radiusM",
              "scale",
              "groundWidthM",
              "groundHeightM",
              "features",
              "sample",
              "attribution",
              "svg",
              "pdf"
            ],
            "properties": {
              "title": {
                "type": "string"
              },
              "paper": {
                "type": "string"
              },
              "orientation": {
                "type": "string"
              },
              "widthMm": {
                "type": "number"
              },
              "heightMm": {
                "type": "number"
              },
              "center": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  }
                }
              },
              "radiusM": {
                "type": "number"
              },
              "scale": {
                "type": "integer",
                "description": "Paper scale 1 : n at the centre."
              },
              "groundWidthM": {
                "type": "number"
              },
              "groundHeightM": {
                "type": "number"
              },
              "features": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Features drawn per layer."
              },
              "sample": {
                "type": "boolean"
              },
              "attribution": {
                "type": "string"
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "pdf": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "CartographRouteArtInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "OpenStreetMap extract (.osm, .osm.pbf or GeoJSON)",
            "description": "Up to 25 MB. Leave empty to use the built-in Vaduz sample. Download extracts from your own OpenStreetMap export, Geofabrik or BBBike; Cartograph never fetches map data itself. Upload as {filename, contentType?, contentBase64}. Max 24414 KiB. Accepts .osm,.pbf,.geojson,.json,application/xml,text/xml,application/geo+json,application/json,application/octet-stream.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "shape": {
            "title": "Shape",
            "type": "string",
            "enum": [
              "heart",
              "star",
              "circle",
              "square",
              "triangle",
              "arrow",
              "zigzag"
            ],
            "default": "heart"
          },
          "center": {
            "title": "Shape centre",
            "description": "Empty: the middle of the extract.",
            "type": "string",
            "default": ""
          },
          "widthM": {
            "title": "Shape width",
            "type": "number",
            "minimum": 100,
            "maximum": 20000,
            "default": 1000,
            "x-unit": "m"
          },
          "rotationDeg": {
            "title": "Rotation (clockwise)",
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "default": 0,
            "x-unit": "°"
          },
          "profile": {
            "title": "Travel mode",
            "type": "string",
            "enum": [
              "foot",
              "bike"
            ],
            "default": "foot"
          },
          "spacingM": {
            "title": "Waypoint spacing",
            "description": "0 = automatic (shape length ÷ 60, at least 30 m).",
            "type": "number",
            "minimum": 0,
            "maximum": 1000,
            "default": 0,
            "x-unit": "m"
          },
          "guidance": {
            "title": "Stay-on-shape weight",
            "description": "0 = plain shortest paths between waypoints; higher values prefer streets close to the drawn line.",
            "type": "number",
            "minimum": 0,
            "maximum": 10,
            "default": 2
          }
        },
        "required": []
      },
      "CartographRouteArtResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "shape",
              "profile",
              "center",
              "widthM",
              "rotationDeg",
              "routeLengthM",
              "shapeLengthM",
              "waypoints",
              "skipped",
              "meanDeviationM",
              "maxDeviationM",
              "coverage",
              "sample",
              "attribution",
              "gpx"
            ],
            "properties": {
              "shape": {
                "type": "string"
              },
              "profile": {
                "type": "string",
                "enum": [
                  "foot",
                  "bike"
                ]
              },
              "center": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  }
                }
              },
              "widthM": {
                "type": "number"
              },
              "rotationDeg": {
                "type": "number"
              },
              "routeLengthM": {
                "type": "number"
              },
              "shapeLengthM": {
                "type": "number"
              },
              "waypoints": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              },
              "meanDeviationM": {
                "type": "number"
              },
              "maxDeviationM": {
                "type": "number"
              },
              "coverage": {
                "type": "number",
                "description": "Share of the shape within 25 m of the route (0–1)."
              },
              "sample": {
                "type": "boolean"
              },
              "attribution": {
                "type": "string"
              },
              "gpx": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "CartographMeetHalfwayInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "people": {
            "title": "People (one per line: “Name: coordinate”)",
            "description": "Coordinates in any format: decimal degrees, DMS, UTM, MGRS, plus codes, geohash. Addresses are not looked up.",
            "type": "string",
            "maxLength": 4000,
            "default": "Berlin: 52.5163, 13.3777\nHamburg: 53.5503, 9.9920\nMunich: 48.1374, 11.5755"
          },
          "useRoads": {
            "title": "Also find the fairest place by road (needs an extract or the Vaduz sample)",
            "type": "boolean",
            "default": false
          },
          "profile": {
            "title": "Travel mode for roads",
            "type": "string",
            "enum": [
              "foot",
              "bike"
            ],
            "default": "foot"
          },
          "file": {
            "title": "OpenStreetMap extract (.osm, .osm.pbf or GeoJSON)",
            "description": "Up to 25 MB. Leave empty to use the built-in Vaduz sample. Download extracts from your own OpenStreetMap export, Geofabrik or BBBike; Cartograph never fetches map data itself. Upload as {filename, contentType?, contentBase64}. Max 24414 KiB. Accepts .osm,.pbf,.geojson,.json,application/xml,text/xml,application/geo+json,application/json,application/octet-stream.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          }
        },
        "required": []
      },
      "CartographMeetHalfwayResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "people",
              "midpoint",
              "fairest",
              "leastTotal",
              "centroid",
              "byRoad",
              "geojson"
            ],
            "properties": {
              "people": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "label",
                    "lat",
                    "lon"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "lat": {
                      "type": "number"
                    },
                    "lon": {
                      "type": "number"
                    }
                  }
                }
              },
              "midpoint": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "fairest": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "leastTotal": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "centroid": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  }
                }
              },
              "byRoad": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon",
                  "distancesM",
                  "maxM",
                  "sumM",
                  "profile"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  },
                  "distancesM": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "maxM": {
                    "type": "number"
                  },
                  "sumM": {
                    "type": "number"
                  },
                  "profile": {
                    "type": "string"
                  }
                }
              },
              "geojson": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "CartographTrueSizeInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "country": {
            "title": "Country (English name or ISO code)",
            "type": "string",
            "maxLength": 60,
            "default": "GRL"
          },
          "target": {
            "title": "Move it to",
            "description": "Coordinate of the new centre (anchor = centre of the bounding box of the largest part).",
            "type": "string",
            "default": "2, 20"
          },
          "rotationDeg": {
            "title": "Rotation (clockwise)",
            "type": "number",
            "minimum": -180,
            "maximum": 180,
            "default": 0,
            "x-unit": "°"
          },
          "compare": {
            "title": "Compare with (optional)",
            "description": "A second country drawn at home and compared by true area.",
            "type": "string",
            "maxLength": 60,
            "default": ""
          }
        },
        "required": []
      },
      "CartographTrueSizeResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "country",
              "iso3",
              "anchor",
              "target",
              "rotationDeg",
              "trueAreaKm2",
              "movedTrueAreaKm2",
              "apparentHomeKm2",
              "apparentTargetKm2",
              "apparentChange",
              "compare",
              "geojson"
            ],
            "properties": {
              "country": {
                "type": "string"
              },
              "iso3": {
                "type": "string"
              },
              "anchor": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  }
                }
              },
              "target": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lat",
                  "lon"
                ],
                "properties": {
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  }
                }
              },
              "rotationDeg": {
                "type": "number"
              },
              "trueAreaKm2": {
                "type": "number"
              },
              "movedTrueAreaKm2": {
                "type": "number"
              },
              "apparentHomeKm2": {
                "type": "number",
                "description": "Planar Web-Mercator area at home, km² at equator scale."
              },
              "apparentTargetKm2": {
                "type": "number"
              },
              "apparentChange": {
                "type": "number",
                "description": "Apparent area at the new place ÷ apparent area at home."
              },
              "compare": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "country",
                  "iso3",
                  "trueAreaKm2",
                  "ratio"
                ],
                "properties": {
                  "country": {
                    "type": "string"
                  },
                  "iso3": {
                    "type": "string"
                  },
                  "trueAreaKm2": {
                    "type": "number"
                  },
                  "ratio": {
                    "type": "number",
                    "description": "True area of the country ÷ true area of the comparison country."
                  }
                }
              },
              "geojson": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}