├── WORKSPACE
├── WORKSPACE.bzlmod
├── .bazelversion
├── production
├── containers
│ ├── BUILD
│ ├── serve.sh
│ ├── Containerfile
│ ├── trails_lat_nginx.conf
│ ├── trailcatalog_nginx.conf
│ └── build.sh
├── pulumi
│ ├── .gitignore
│ ├── Pulumi.prod.yaml
│ ├── requirements.txt
│ ├── Pulumi.yaml
│ └── __main__.py
└── files
│ ├── README.md
│ ├── network-ifs.nix
│ ├── trailcatalog_frontend.sh
│ ├── provision.sh
│ └── trails_lat_frontend.sh
├── requirements_lock.in
├── js
├── map
│ ├── workers
│ │ ├── mbtile_types.ts
│ │ ├── raster_loader.ts
│ │ └── BUILD
│ ├── common
│ │ ├── dpi.ts
│ │ ├── BUILD
│ │ ├── bounds_quadtree.test.ts
│ │ ├── math.test.ts
│ │ └── types.ts
│ ├── z.ts
│ ├── layers
│ │ ├── BUILD
│ │ └── skybox_layer.ts
│ ├── BUILD
│ ├── rendering
│ │ ├── BUILD
│ │ └── texture_pool.ts
│ ├── events.ts
│ ├── layer.ts
│ └── copyright_dialog.tsx
├── s2viewer
│ ├── run.sh
│ ├── app.css
│ ├── index.html
│ └── nginx.conf
└── dino
│ ├── BUILD
│ ├── select.tsx
│ ├── checkbox.tsx
│ ├── input.tsx
│ ├── radio.tsx
│ ├── button.tsx
│ └── fabric.tsx
├── java
├── org
│ └── trailcatalog
│ │ ├── s2
│ │ ├── package.json
│ │ ├── SimpleS2.ts
│ │ ├── S2Earth.kt
│ │ └── index.ts
│ │ ├── static
│ │ ├── images
│ │ │ ├── icons
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── github.png
│ │ │ │ ├── bear-face.png
│ │ │ │ ├── blue-dot.png
│ │ │ │ ├── boundary.svg
│ │ │ │ ├── blue-dot.svg
│ │ │ │ ├── README.md
│ │ │ │ ├── boundary-filled.svg
│ │ │ │ ├── trail.svg
│ │ │ │ └── national-park.svg
│ │ │ ├── atlases
│ │ │ │ ├── points.png
│ │ │ │ └── points
│ │ │ │ │ ├── peak.svg
│ │ │ │ │ ├── point.svg
│ │ │ │ │ ├── saddle.svg
│ │ │ │ │ ├── camp_site.svg
│ │ │ │ │ ├── mountain_pass.svg
│ │ │ │ │ ├── wilderness_hut.svg
│ │ │ │ │ ├── guidepost.svg
│ │ │ │ │ ├── shelter.svg
│ │ │ │ │ ├── alpine_hut.svg
│ │ │ │ │ ├── cave_entrance.svg
│ │ │ │ │ ├── drinking_water.svg
│ │ │ │ │ ├── picnic_table.svg
│ │ │ │ │ ├── bridge.svg
│ │ │ │ │ ├── viewpoint.svg
│ │ │ │ │ ├── barbecue.svg
│ │ │ │ │ ├── parking.svg
│ │ │ │ │ ├── waterfall.svg
│ │ │ │ │ ├── toilets.svg
│ │ │ │ │ ├── firepit.svg
│ │ │ │ │ ├── def.json
│ │ │ │ │ ├── volcano.svg
│ │ │ │ │ ├── trailhead.svg
│ │ │ │ │ └── visitor_center.svg
│ │ │ └── logomark.svg
│ │ └── BUILD
│ │ ├── flags
│ │ ├── FlagSpec.kt
│ │ ├── BUILD
│ │ └── Flag.kt
│ │ ├── importers
│ │ ├── pbf
│ │ │ ├── WaySkeleton.java
│ │ │ ├── Relation.java
│ │ │ ├── Way.java
│ │ │ ├── Node.java
│ │ │ ├── Point.kt
│ │ │ ├── LatLngE7.java
│ │ │ ├── LatLngRectE7.java
│ │ │ ├── ExtractNodeWayPairs.kt
│ │ │ ├── GatherWayNodes.kt
│ │ │ ├── BUILD
│ │ │ ├── GatherRelationWays.kt
│ │ │ ├── MakeWayGeometries.kt
│ │ │ ├── ExtractNodes.kt
│ │ │ └── ExtractWayRelationPairs.kt
│ │ ├── basemap
│ │ │ ├── Boundary.java
│ │ │ ├── Profile.kt
│ │ │ ├── Trail.java
│ │ │ ├── Geometry.kt
│ │ │ ├── DisambiguateTrailIds.kt
│ │ │ ├── InnerJoinWays.kt
│ │ │ ├── DumpReadableTrailIds.kt
│ │ │ ├── DumpTrailsInBoundaries.kt
│ │ │ ├── GatherTrailBoundaries.kt
│ │ │ ├── DumpBoundariesInBoundaries.kt
│ │ │ ├── DumpPathsInTrails.kt
│ │ │ ├── UpdateWayElevations.kt
│ │ │ ├── DumpBoundaries.kt
│ │ │ ├── DumpPathElevations.kt
│ │ │ ├── ExtractWaysFromFilteredRelations.kt
│ │ │ ├── CreateTrailsInBoundaries.kt
│ │ │ └── CreateBoundariesInBoundaries.kt
│ │ ├── pipeline
│ │ │ ├── README.md
│ │ │ ├── collections
│ │ │ │ ├── Emitter.kt
│ │ │ │ ├── PCollection.kt
│ │ │ │ ├── FileReference.kt
│ │ │ │ ├── Serializer.kt
│ │ │ │ ├── DisposableSupplier.kt
│ │ │ │ ├── PList.kt
│ │ │ │ ├── PSortedList.kt
│ │ │ │ ├── BUILD
│ │ │ │ └── PMap.kt
│ │ │ ├── progress
│ │ │ │ └── BUILD
│ │ │ ├── io
│ │ │ │ ├── BUILD
│ │ │ │ └── ByteBufferEncodedOutputStream.kt
│ │ │ ├── BUILD
│ │ │ ├── PSink.kt
│ │ │ ├── PStage.kt
│ │ │ ├── InvertMap.kt
│ │ │ ├── UniqueValues.kt
│ │ │ ├── PSource.kt
│ │ │ ├── PSortedTransformer.kt
│ │ │ ├── PMapTransformer.kt
│ │ │ ├── BinaryStructListWriter.kt
│ │ │ └── BinaryStructListReader.kt
│ │ ├── elevation
│ │ │ ├── DemMetadata.kt
│ │ │ ├── BUILD
│ │ │ ├── tiff
│ │ │ │ └── BUILD
│ │ │ └── contour
│ │ │ │ └── BUILD.bazel
│ │ └── common
│ │ │ ├── BUILD
│ │ │ ├── ClosedIntRange.kt
│ │ │ └── ProgressBar.kt
│ │ ├── common
│ │ ├── IORuntimeException.kt
│ │ ├── BUILD
│ │ ├── AlignableByteArrayOutputStream.kt
│ │ ├── DelegatingEncodedOutputStream.kt
│ │ ├── GeoTiff.kt
│ │ ├── Tiff.kt
│ │ └── EncodedByteBufferInputStream.kt
│ │ ├── client
│ │ ├── workers
│ │ │ ├── data_constants.ts
│ │ │ └── BUILD
│ │ ├── title.ts
│ │ ├── models
│ │ │ └── BUILD
│ │ ├── common
│ │ │ ├── BUILD
│ │ │ ├── data.ts
│ │ │ ├── types.ts
│ │ │ ├── base64.ts
│ │ │ ├── weather.ts
│ │ │ └── math.ts
│ │ ├── map
│ │ │ ├── events.ts
│ │ │ ├── BUILD
│ │ │ └── colors.ts
│ │ ├── data
│ │ │ └── BUILD
│ │ ├── boundary_crumbs.tsx
│ │ ├── BUILD
│ │ ├── app.css
│ │ ├── unit_selector_element.tsx
│ │ ├── go_to_trail_element.tsx
│ │ ├── unit_selector_controller.ts
│ │ ├── routes.ts
│ │ └── route_controller.ts
│ │ ├── PgSupport.kt
│ │ ├── EpochTracker.kt
│ │ ├── run.sh
│ │ ├── nginx.conf
│ │ ├── models
│ │ ├── ToTypeScript.kt
│ │ └── BUILD
│ │ ├── frontend
│ │ ├── BUILD
│ │ └── server.ts
│ │ └── scrapers
│ │ └── BUILD.bazel
└── lat
│ └── trails
│ ├── common
│ ├── Levels.kt
│ ├── BUILD
│ └── Database.kt
│ ├── client
│ ├── login_element.tsx
│ ├── app.css
│ ├── workers
│ │ └── BUILD
│ ├── BUILD
│ ├── citations_element.tsx
│ └── app.tsx
│ ├── nginx.conf
│ ├── importers
│ └── BUILD
│ ├── frontend
│ ├── BUILD
│ ├── encrypter.ts
│ └── auth.ts
│ ├── run.sh
│ ├── BUILD
│ └── static
│ └── BUILD
├── trailcatalog
├── containers
│ └── aria2.Containerfile
├── migrations
│ ├── 1_create_sources.toml
│ ├── 2_search.toml
│ ├── 1_enum_function.toml
│ ├── 1_create_points.toml
│ ├── 1_create_readable_ids.toml
│ └── 1_create_elevation.toml
├── proto
│ ├── BUILD
│ └── encoding.proto
└── elevation
│ └── fetch_datasets.py
├── third_party
├── deanc-esbuild-plugin-postcss
│ ├── README.md
│ ├── BUILD
│ ├── LICENSE
│ └── index.js
└── mapbox-vector-tiles
│ └── BUILD
├── .gitignore
├── javatests
└── org
│ └── trailcatalog
│ ├── importers
│ ├── pipeline
│ │ ├── SequenceSource.kt
│ │ ├── Debug.kt
│ │ ├── BUILD
│ │ ├── io
│ │ │ ├── BUILD
│ │ │ └── EncodedStreamsTest.kt
│ │ └── Sum.kt
│ ├── elevation
│ │ └── contour
│ │ │ └── BUILD
│ └── basemap
│ │ ├── CreateTrailsTest.kt
│ │ └── BUILD.bazel
│ ├── s2
│ └── BUILD
│ └── models
│ ├── BUILD
│ └── CategoriesTest.kt
├── py
├── README.md
└── BUILD
├── README.md
├── BUILD
├── COPYRIGHT
├── .bazelrc
├── tsconfig.json
├── default.nix
└── package.json
/WORKSPACE:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/WORKSPACE.bzlmod:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.bazelversion:
--------------------------------------------------------------------------------
1 | 8.2.1
2 |
--------------------------------------------------------------------------------
/production/containers/BUILD:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/production/pulumi/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | venv/
3 |
--------------------------------------------------------------------------------
/production/pulumi/Pulumi.prod.yaml:
--------------------------------------------------------------------------------
1 | config:
2 | gcp:project: trailcatalog
3 |
--------------------------------------------------------------------------------
/requirements_lock.in:
--------------------------------------------------------------------------------
1 | osmium==3.7.0
2 | reportlab==4.2.0
3 | svgutils==0.3.4
4 |
--------------------------------------------------------------------------------
/production/pulumi/requirements.txt:
--------------------------------------------------------------------------------
1 | pulumi>=3.0.0,<4.0.0
2 | pulumi-gcp>=6.0.0,<7.0.0
3 |
--------------------------------------------------------------------------------
/js/map/workers/mbtile_types.ts:
--------------------------------------------------------------------------------
1 | export enum GeometryType {
2 | Point = 1,
3 | Line = 2,
4 | Polygon = 3,
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/js/map/common/dpi.ts:
--------------------------------------------------------------------------------
1 | export const DPI = Math.max(window.devicePixelRatio ?? 1, 2);
2 |
3 | export const DPI_ZOOM = Math.log2(DPI) - 1;
4 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/s2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "s2_raw",
3 | "main": "s2_raw.js",
4 | "type": "module",
5 | "typings": "s2_raw.d.ts"
6 | }
7 |
--------------------------------------------------------------------------------
/trailcatalog/containers/aria2.Containerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3
2 | RUN apk add --no-cache aria2
3 | RUN adduser -D app
4 | USER app
5 | ENTRYPOINT ["aria2c"]
6 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/s2/SimpleS2.ts:
--------------------------------------------------------------------------------
1 | import * as raw from 'java/org/trailcatalog/s2/s2_raw';
2 |
3 | export const SimpleS2 = raw.org.trailcatalog.s2.SimpleS2;
4 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aschleck/trailcatalog/HEAD/java/org/trailcatalog/static/images/icons/favicon.ico
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aschleck/trailcatalog/HEAD/java/org/trailcatalog/static/images/icons/github.png
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aschleck/trailcatalog/HEAD/java/org/trailcatalog/static/images/atlases/points.png
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/bear-face.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aschleck/trailcatalog/HEAD/java/org/trailcatalog/static/images/icons/bear-face.png
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/blue-dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aschleck/trailcatalog/HEAD/java/org/trailcatalog/static/images/icons/blue-dot.png
--------------------------------------------------------------------------------
/production/pulumi/Pulumi.yaml:
--------------------------------------------------------------------------------
1 | name: trailcatalog
2 | runtime:
3 | name: python
4 | options:
5 | virtualenv: venv
6 | description: Trails and paths oh my
7 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/flags/FlagSpec.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.flags
2 |
3 | @Retention
4 | @Target(AnnotationTarget.FIELD)
5 | annotation class FlagSpec(val name: String)
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/WaySkeleton.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | public record WaySkeleton(long id, int type, String name, long[] nodes) {}
4 |
--------------------------------------------------------------------------------
/java/lat/trails/common/Levels.kt:
--------------------------------------------------------------------------------
1 | package lat.trails.common
2 |
3 | const val COLLECTION_COVERING_MAX_LEVEL = 7 // TRAILS_LAT_S2_INDEX_LEVEL?
4 | const val FEATURE_COVERING_MAX_LEVEL = 14
5 |
--------------------------------------------------------------------------------
/third_party/deanc-esbuild-plugin-postcss/README.md:
--------------------------------------------------------------------------------
1 | # deanc-esbuild-plugin-postcss
2 |
3 | This is a fork of https://github.com/deanc/esbuild-plugin-postcss that plays better with rules_js.
4 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/Boundary.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap;
2 |
3 | public record Boundary(long id, int type, long cell, String name, byte[] s2Polygon) {}
4 |
--------------------------------------------------------------------------------
/production/files/README.md:
--------------------------------------------------------------------------------
1 | # Files to push to the server
2 |
3 | 1. Download the frontend secrets key from GCP
4 | 1. Push the rest
5 | 1. `sudo nixos-generate-config`
6 | 1. `sudo nixos-rebuild switch`
7 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/Relation.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | import org.trailcatalog.proto.RelationSkeleton;
4 |
5 | public record Relation(long id, int type, String name, RelationSkeleton skeleton) {}
6 |
--------------------------------------------------------------------------------
/trailcatalog/migrations/1_create_sources.toml:
--------------------------------------------------------------------------------
1 | [[actions]]
2 | type = "create_table"
3 | name = "active_epoch"
4 | primary_key = ["epoch"]
5 |
6 | [[actions.columns]]
7 | name = "epoch"
8 | nullable = false
9 | type = "INT"
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.etag
2 | *.osc
3 | *.pbf
4 | *.tif
5 | .DS_Store
6 | ._.DS_Store
7 | .ijwb
8 | __pycache__
9 | bazel-*
10 | elevation_profiles.pb
11 | elevation_profiles.pb.shards
12 | frontend_pkg.tar
13 | full_pkg.tar
14 | node_modules
15 | venv
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/IORuntimeException.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.common
2 |
3 | import java.lang.RuntimeException
4 |
5 | open class IORuntimeException(message: String, throwable: Throwable? = null)
6 | : RuntimeException(message, throwable)
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/README.md:
--------------------------------------------------------------------------------
1 | # A shameful pipeline framework
2 |
3 | ## Was writing this a bad idea?
4 |
5 | Yes.
6 |
7 | ## Shouldn't you have just used Beam?
8 |
9 | Probably.
10 |
11 | ## Does it work?
12 |
13 | Mostly.
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/pipeline/SequenceSource.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | class SequenceSource(private val sequence: Sequence) : PSource() {
4 |
5 | override fun read(): Sequence = sequence
6 | }
--------------------------------------------------------------------------------
/js/s2viewer/run.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | cd $(dirname "$0")
3 | cd ../../
4 | bazelisk build //js/s2viewer:s2_pkg
5 | nginx \
6 | -c "$(pwd)/js/s2viewer/nginx.conf" \
7 | -e /dev/stderr \
8 | -p "$(pwd)" &
9 | trap 'kill $(jobs -p)' EXIT
10 | sleep 200d
11 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/Emitter.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | interface Emitter {
4 |
5 | fun emit(v: T)
6 | }
7 |
8 | interface Emitter2 {
9 |
10 | fun emit(a: A, b: B)
11 | }
12 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/PCollection.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import java.io.Closeable
4 |
5 | interface PCollection : Closeable, Iterator {
6 |
7 | fun estimatedByteSize(): Long
8 | }
9 |
--------------------------------------------------------------------------------
/java/lat/trails/client/login_element.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | export function LoginElement() {
4 | return <>
5 | Login
6 | Login with Google
7 | >;
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/boundary.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/progress/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "progress",
7 | srcs = glob(["*.kt"]),
8 | )
9 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/blue-dot.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/js/map/common/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "common",
7 | deps = [
8 | "@dev_april_corgi//js/common",
9 | ],
10 | )
11 |
12 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/Way.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | import java.util.List;
4 |
5 | public record Way(
6 | long id,
7 | int hash,
8 | int type,
9 | float downMeters,
10 | float upMeters,
11 | List points) {}
12 |
--------------------------------------------------------------------------------
/js/dino/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//build_defs:ts.bzl", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "dino",
7 | deps = [
8 | "@dev_april_corgi//js/corgi",
9 | "@dev_april_corgi//js/emu",
10 | ],
11 | )
12 |
13 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/peak.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/point.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/workers/data_constants.ts:
--------------------------------------------------------------------------------
1 | import { S2CellNumber } from '../common/types';
2 |
3 | export const COARSE_ZOOM_THRESHOLD = 10;
4 | export const FINE_ZOOM_THRESHOLD = 12;
5 |
6 | // There is no S2 cell ID 0, so we overload it for this.
7 | export const PIN_CELL_ID = 0 as S2CellNumber;
8 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/elevation/DemMetadata.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.elevation
2 |
3 | import com.google.common.geometry.S2LatLngRect
4 |
5 | data class DemMetadata(
6 | val id: String,
7 | val bounds: S2LatLngRect,
8 | val url: String,
9 | val global: Boolean,
10 | )
--------------------------------------------------------------------------------
/trailcatalog/proto/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | proto_library(
4 | name = "encoding_proto",
5 | srcs = ["encoding.proto"],
6 | )
7 |
8 | java_proto_library(
9 | name = "encoding_java_proto",
10 | deps = [":encoding_proto"],
11 | )
12 |
--------------------------------------------------------------------------------
/third_party/mapbox-vector-tiles/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | proto_library(
4 | name = "vector_tile_proto",
5 | srcs = ["vector_tile.proto"],
6 | )
7 |
8 | java_proto_library(
9 | name = "vector_tile_java_proto",
10 | deps = [":vector_tile_proto"],
11 | )
12 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/FileReference.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import java.io.Closeable
4 | import java.io.File
5 |
6 | data class FileReference(private val file: File) : Closeable {
7 |
8 | override fun close() {
9 | file.delete()
10 | }
11 | }
--------------------------------------------------------------------------------
/js/s2viewer/app.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @theme {
4 | --animate-slide: slide 1.75s ease-in-out infinite;
5 | @keyframes slide {
6 | 0% {
7 | left: -33%;
8 | }
9 | 100% {
10 | left: 100%;
11 | }
12 | }
13 | }
14 |
15 | a:not(.no-underline):hover {
16 | @apply underline;
17 | }
18 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/s2/S2Earth.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.s2
2 |
3 | import com.google.common.geometry.S1Angle
4 |
5 | fun earthMetersToAngle(meters: Double): S1Angle {
6 | return SimpleS2.earthMetersToAngle(meters)
7 | }
8 |
9 | fun S1Angle.earthMeters(): Double {
10 | return SimpleS2.angleToEarthMeters(this)
11 | }
12 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/title.ts:
--------------------------------------------------------------------------------
1 | import { setTitle as ssrSetTitle } from 'external/dev_april_corgi+/js/server/ssr_aware';
2 |
3 | export function setTitle(title: string|undefined): void {
4 | if (title) {
5 | ssrSetTitle(`${title} - Trailcatalog`);
6 | } else {
7 | ssrSetTitle('Trailcatalog');
8 | }
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "common",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "@maven//:com_google_guava_guava",
10 | ],
11 | )
12 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/models/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "models",
7 | deps = [
8 | "//java/org/trailcatalog/client/common",
9 | "//js/map/common",
10 | ],
11 | )
12 |
13 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/saddle.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/camp_site.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/mountain_pass.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/third_party/deanc-esbuild-plugin-postcss/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@aspect_rules_js//js:defs.bzl", "js_library")
4 |
5 | js_library(
6 | name = "deanc-esbuild-plugin-postcss",
7 | srcs = [
8 | "index.js",
9 | ],
10 | deps = [
11 | "//:node_modules/postcss",
12 | ],
13 | )
14 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/Serializer.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import org.trailcatalog.common.EncodedInputStream
4 | import org.trailcatalog.common.EncodedOutputStream
5 |
6 | interface Serializer {
7 | fun read(from: EncodedInputStream): T
8 | fun write(v: T, to: EncodedOutputStream)
9 | }
10 |
--------------------------------------------------------------------------------
/js/map/z.ts:
--------------------------------------------------------------------------------
1 | export const Z_BOTTOM = 0;
2 | export const Z_BASE_TERRAIN = 1;
3 | export const Z_BASE_WATER = 2;
4 | export const Z_BASE_SATELLITE = 3;
5 | export const Z_OVERLAY_TERRAIN = 10;
6 | export const Z_OVERLAY_TRANSPORTATION = 12;
7 | export const Z_OVERLAY_TEXT = 13;
8 | export const Z_USER_DATA = 100;
9 |
10 | // don't exceed 1000 without changing program.ts.
11 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/wilderness_hut.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/flags/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "flags",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "@maven//:com_google_guava_guava",
10 | "@maven//:org_reflections_reflections",
11 | ],
12 | )
13 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/io/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "io",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "//java/org/trailcatalog/common",
10 | "@maven//:com_google_guava_guava",
11 | ],
12 | )
13 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/AlignableByteArrayOutputStream.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.common
2 |
3 | import java.io.ByteArrayOutputStream
4 |
5 | class AlignableByteArrayOutputStream : ByteArrayOutputStream() {
6 |
7 | fun align(alignment: Int) {
8 | count = (count + alignment - 1) / alignment * alignment
9 | // No need to grow because the next write will catch up
10 | }
11 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/flags/Flag.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.flags
2 |
3 | abstract class Flag(
4 | private var _value: T,
5 | val defaultMissingValue: String? = null,
6 | val requireValue: Boolean = true) {
7 |
8 | val value: T get() = this._value
9 |
10 | fun parseAndSave(s: String) {
11 | _value = parseFrom(s)
12 | }
13 |
14 | protected abstract fun parseFrom(s: String): T;
15 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/Profile.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import java.io.File
4 | import org.trailcatalog.importers.pbf.LatLngE7
5 |
6 | var ELEVATION_PROFILES_FILE = File("elevation_profiles.pb")
7 |
8 | data class Profile(
9 | val id: Long,
10 | val hash: Int,
11 | val down: Double,
12 | val up: Double,
13 | val profile: List,
14 | )
15 |
--------------------------------------------------------------------------------
/js/map/layers/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "layers",
7 | deps = [
8 | "//js/map",
9 | "//js/map/common",
10 | "//js/map/rendering",
11 | "//js/map/workers",
12 | "@dev_april_corgi//js/server:client",
13 | ],
14 | )
15 |
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/Trail.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap;
2 |
3 | import com.google.common.geometry.S2Polyline;
4 | import java.util.List;
5 |
6 | public record Trail(
7 | long relationId,
8 | int type,
9 | String name,
10 | long[] paths,
11 | S2Polyline polyline,
12 | float downMeters,
13 | float upMeters,
14 | boolean validGeometry) {
15 | }
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "pipeline",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "//java/org/trailcatalog/importers/pipeline/collections",
10 | "@maven//:com_google_guava_guava",
11 | ],
12 | )
13 |
--------------------------------------------------------------------------------
/js/map/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "map",
7 | deps = [
8 | "//java/org/trailcatalog/s2",
9 | "//js/map/common",
10 | "//js/map/rendering",
11 | "@dev_april_corgi//js/corgi",
12 | "@dev_april_corgi//js/emu",
13 | ],
14 | )
15 |
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/guidepost.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/Node.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | public record Node(long id, LatLngE7 latLng) implements Comparable {
4 |
5 | @Override
6 | public int compareTo(Node o) {
7 | var delta = id - o.id;
8 | if (delta < 0) {
9 | return -1;
10 | } else if (delta == 0) {
11 | return 0;
12 | } else {
13 | return 1;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/DisposableSupplier.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import java.io.Closeable
4 |
5 | class DisposableSupplier(private val disposer: Closeable, private val value: () -> V)
6 | : Closeable {
7 |
8 | fun invoke(): V {
9 | return value.invoke()
10 | }
11 |
12 | override fun close() {
13 | disposer.close()
14 | }
15 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/PList.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import com.google.common.reflect.TypeToken
4 |
5 | interface PList : PCollection
6 |
7 | fun createPList(
8 | type: TypeToken,
9 | estimatedByteSize: Long,
10 | fn: (Emitter) -> Unit): DisposableSupplier> {
11 | return createMmapPList(type, fn)
12 | }
13 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/shelter.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/README.md:
--------------------------------------------------------------------------------
1 | bear_face.svg:
2 | "u1f43b.svg" from https://github.com/adobe-fonts/noto-emoji-svg under Apache 2.0
3 |
4 | national-park.svg:
5 | "National Park" by Adrien Coquet, https://thenounproject.com/icon/national-park-3202030/ under purchased license
6 |
7 | osm-logo.svg:
8 | By Ken Vermette under the Creative Commons Attribution-ShareAlike 3.0 (https://creativecommons.org/licenses/by-sa/3.0/)
9 |
--------------------------------------------------------------------------------
/py/README.md:
--------------------------------------------------------------------------------
1 | # Python scripts that do things
2 |
3 | ## svgs_to_atlas.py
4 |
5 | Generates an SVG atlas composed of the images in the given definition. Also spits out a TypeScript
6 | struct to make client-side usage easy.
7 |
8 | ```
9 | python svgs_to_atlas.py \
10 | -r 4 \
11 | ../java/org/trailcatalog/static/images/atlases/points/def.json \
12 | ../java/org/trailcatalog/static/images/atlases/points.png
13 | ```
14 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/pipeline/Debug.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import org.trailcatalog.importers.pipeline.collections.PCollection
4 |
5 | class Debug> : PSink() {
6 |
7 | val values = ArrayList()
8 |
9 | override fun write(input: T) {
10 | while (input.hasNext()) {
11 | values.add(input.next().toString())
12 | }
13 | input.close()
14 | }
15 | }
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/s2/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
4 |
5 | kt_jvm_test(
6 | name = "s2",
7 | srcs = glob(["*.kt"]),
8 | test_class = "org.trailcatalog.s2.SimpleS2Test",
9 | deps = [
10 | "//java/org/trailcatalog/s2:s2-java",
11 | "@maven//:com_google_truth_truth",
12 | "@maven//:org_junit_jupiter_junit_jupiter",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/common/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "common",
7 | deps = [
8 | "//java/org/trailcatalog/s2",
9 | "//js/dino",
10 | "//js/map/common",
11 | "@dev_april_corgi//js/corgi",
12 | "@dev_april_corgi//js/corgi/history",
13 | "@dev_april_corgi//js/server:client",
14 | ],
15 | )
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/map/events.ts:
--------------------------------------------------------------------------------
1 | import { declareEvent } from 'external/dev_april_corgi+/js/corgi/events';
2 | import { Vec2 } from 'js/map/common/types';
3 |
4 | import { Path, Point, Trail } from '../models/types';
5 |
6 | export const HOVER_CHANGED = declareEvent<{
7 | target: Path|Point|Trail|undefined;
8 | }>('hover_changed');
9 |
10 | export const SELECTION_CHANGED = declareEvent<{
11 | selected?: Path|Point|Trail;
12 | clickPx: Vec2;
13 | }>('selection_changed');
14 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/common/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "common",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "//java/org/trailcatalog/common",
10 | "@maven//:com_google_guava_guava",
11 | "@maven//:com_squareup_okhttp3_okhttp",
12 | "@maven//:org_postgresql_postgresql",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/production/containers/serve.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -ex
4 |
5 | nginx -c '/app/nginx.conf' -e '/dev/stderr' &
6 | nginx_pid="$!"
7 | ${NODEJS_DIR}/bin/node --enable-source-maps '/app/frontend.js' &
8 | node_pid="$!"
9 | java -jar '/app/api_server_deploy.jar' "$@" &
10 | fe_pid="$!"
11 |
12 | # Wait for the first process to exit
13 | wait -n "${nginx_pid}" "${node_pid}" "${fe_pid}"
14 | exit_code="$?"
15 | kill "${nginx_pid}" "${node_pid}" "${fe_pid}"
16 | exit "${exit_code}"
17 |
18 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/models/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
4 |
5 | kt_jvm_test(
6 | name = "models",
7 | srcs = glob(["*.kt"]),
8 | test_class = "org.trailcatalog.models.CategoriesTest",
9 | deps = [
10 | "//java/org/trailcatalog/models",
11 | "@maven//:com_google_truth_truth",
12 | "@maven//:org_junit_jupiter_junit_jupiter",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/data/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "data",
7 | deps = [
8 | "//java/org/trailcatalog/client/models",
9 | "//java/org/trailcatalog/client/workers:ts",
10 | "@dev_april_corgi//js/common",
11 | "@dev_april_corgi//js/corgi",
12 | "//js/map",
13 | "//js/map/common",
14 | ],
15 | )
16 |
17 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/Point.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | data class Point(
4 | val id: Long,
5 | val type: Int,
6 | val name: String?,
7 | val latLng: LatLngE7,
8 | ) : Comparable {
9 |
10 | override fun compareTo(other: Point): Int {
11 | val delta = id - other.id;
12 | if (delta < 0L) {
13 | return -1;
14 | } else if (delta == 0L) {
15 | return 0;
16 | } else {
17 | return 1;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/PSortedList.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import com.google.common.reflect.TypeToken
4 |
5 | interface PSortedList : PList {
6 | fun find(needle: (v: T) -> Int): T?
7 | }
8 |
9 | fun > createPSortedList(
10 | type: TypeToken, estimatedByteSize: Long, fn: (Emitter) -> Unit):
11 | DisposableSupplier> {
12 | return createMmapPSortedList(type, fn)
13 | }
14 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/io/ByteBufferEncodedOutputStream.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.io
2 |
3 | import java.nio.ByteBuffer
4 | import org.trailcatalog.common.EncodedOutputStream
5 |
6 | class ByteBufferEncodedOutputStream(private val buffer: ByteBuffer) : EncodedOutputStream() {
7 |
8 | override fun write(b: Byte) {
9 | buffer.put(b)
10 | }
11 |
12 | override fun write(b: ByteArray, off: Int, len: Int) {
13 | buffer.put(b, off, len)
14 | }
15 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/alpine_hut.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/js/map/rendering/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "rendering",
7 | deps = [
8 | "//:node_modules/@mapbox/tiny-sdf",
9 | "//:node_modules/@types/node",
10 | "//:node_modules/grapheme-splitter",
11 | "//js/map/common",
12 | "@dev_april_corgi//js/common",
13 | "@dev_april_corgi//js/server:client",
14 | ],
15 | )
16 |
17 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/workers/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | esbuild_binary(
6 | name = "data_fetcher_worker",
7 | entry_point = "data_fetcher.ts",
8 | deps = [
9 | ":ts",
10 | ],
11 | )
12 |
13 | tc_ts_project(
14 | name = "ts",
15 | deps = [
16 | "//java/org/trailcatalog/client/common",
17 | "//java/org/trailcatalog/s2",
18 | ],
19 | )
20 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/cave_entrance.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/pipeline/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
4 |
5 | kt_jvm_test(
6 | name = "pipeline",
7 | srcs = glob(["*.kt"]),
8 | test_class = "org.trailcatalog.importers.pipeline.PipelineTest",
9 | deps = [
10 | "//java/org/trailcatalog/importers/pipeline",
11 | "@maven//:com_google_truth_truth",
12 | "@maven//:org_junit_jupiter_junit_jupiter",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/pipeline/io/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
4 |
5 | kt_jvm_test(
6 | name = "io",
7 | srcs = glob(["*.kt"]),
8 | test_class = "org.trailcatalog.importers.pipeline.io.EncodedStreamsTest",
9 | deps = [
10 | "//java/org/trailcatalog/importers/pipeline/io",
11 | "@maven//:com_google_truth_truth",
12 | "@maven//:org_junit_jupiter_junit_jupiter",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "collections",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "//java/org/trailcatalog/importers/pipeline/io",
10 | "//java/org/trailcatalog/importers/pipeline/progress",
11 | "@maven//:com_google_guava_guava",
12 | "@maven//:com_google_protobuf_protobuf_java",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/drinking_water.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/elevation/contour/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
4 |
5 | kt_jvm_test(
6 | name = "contour",
7 | srcs = glob(["*.kt"]),
8 | test_class = "org.trailcatalog.importers.elevation.contour.CommonTest",
9 | deps = [
10 | "//java/org/trailcatalog/importers/elevation/contour",
11 | "@maven//:com_google_truth_truth",
12 | "@maven//:org_junit_jupiter_junit_jupiter",
13 | ],
14 | )
15 |
--------------------------------------------------------------------------------
/java/lat/trails/common/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_java//java:defs.bzl", "java_binary")
4 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
5 |
6 | kt_jvm_library(
7 | name = "common",
8 | srcs = glob(["*.kt"]),
9 | runtime_deps = [
10 | "@maven//:org_postgresql_postgresql",
11 | ],
12 | deps = [
13 | "//java/org/trailcatalog/flags",
14 | "@maven//:com_zaxxer_HikariCP",
15 | "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8",
16 | ],
17 | )
18 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/DelegatingEncodedOutputStream.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.common
2 |
3 | import java.io.OutputStream
4 |
5 | class DelegatingEncodedOutputStream(private val delegate: OutputStream) : EncodedOutputStream() {
6 |
7 | override fun close() {
8 | delegate.close()
9 | }
10 |
11 | override fun flush() {
12 | delegate.flush()
13 | }
14 |
15 | override fun write(b: Byte) {
16 | delegate.write(b.toInt())
17 | }
18 |
19 | override fun write(b: ByteArray, off: Int, len: Int) {
20 | delegate.write(b, off, len)
21 | }
22 | }
--------------------------------------------------------------------------------
/trailcatalog/elevation/fetch_datasets.py:
--------------------------------------------------------------------------------
1 | https://thor-f5.er.usgs.gov/ngtoc/metadata/waf/elevation/1_meter/geotiff/
2 | https://apps.nationalmap.gov/tnmaccess/#/product
3 | https://tnmaccess.nationalmap.gov/api/v1/products?datasets=Digital%20Elevation%20Model%20(DEM)%201%20meter&prodFormats=GeoTIFF&offset=0&max=500
4 | https://tnmaccess.nationalmap.gov/api/v1/products?datasets=National%20Elevation%20Dataset%20(NED)%201/3%20arc-second,Digital%20Elevation%20Model%20(DEM)%201%20meter,National%20Elevation%20Dataset%20(NED)%201/9%20arc-second&prodFormats=GeoTIFF&offset=0&max=5
5 | "sourceId"
6 |
--------------------------------------------------------------------------------
/js/map/events.ts:
--------------------------------------------------------------------------------
1 | import { S2LatLng } from 'java/org/trailcatalog/s2';
2 | import { declareEvent } from 'external/dev_april_corgi+/js/corgi/events';
3 |
4 | import { LatLngRect, LatLngZoom, Vec2 } from './common/types';
5 |
6 | export const CLICKED = declareEvent<{
7 | clickPx: Vec2;
8 | contextual: boolean;
9 | }>('clicked');
10 |
11 | export const DATA_CHANGED = declareEvent<{}>('data_changed');
12 |
13 | export const MAP_MOVED = declareEvent<{
14 | center: S2LatLng;
15 | zoom: number;
16 | }>('map_moved');
17 |
18 | export const ZOOMED = declareEvent<{}>('zoomed');
19 |
--------------------------------------------------------------------------------
/js/map/common/bounds_quadtree.test.ts:
--------------------------------------------------------------------------------
1 | import { WorldBoundsQuadtree } from './bounds_quadtree';
2 | import { Rect } from './types';
3 |
4 | test('finds intersecting bound', () => {
5 | const qt = new WorldBoundsQuadtree();
6 | qt.insert('a bound', {
7 | low: [-0.676188353888889, 0.29574154468566277],
8 | high: [-0.6759365516666668, 0.29587655213844616],
9 | } as Rect);
10 |
11 | const results: string[] = [];
12 | const query =
13 | qt.queryCircle([-0.6761819853825033, 0.2957417081612863], 1.76943513605359e-7, results);
14 | expect(results.length).toBe(1);
15 | });
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/LatLngE7.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | import com.google.common.geometry.S2LatLng;
4 | import com.google.common.geometry.S2Point;
5 |
6 | public record LatLngE7(int lat, int lng) {
7 |
8 | public static LatLngE7 fromS2LatLng(S2LatLng latLng) {
9 | return new LatLngE7(latLng.lat().e7(), latLng.lng().e7());
10 | }
11 |
12 | public static LatLngE7 fromS2Point(S2Point point) {
13 | return fromS2LatLng(new S2LatLng(point));
14 | }
15 |
16 | public S2LatLng toS2LatLng() {
17 | return S2LatLng.fromE7(lat, lng);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/PSink.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import org.trailcatalog.importers.pipeline.collections.DisposableSupplier
4 | import org.trailcatalog.importers.pipeline.progress.longProgress
5 |
6 | abstract class PSink : PStage() {
7 |
8 | abstract fun write(input: T)
9 |
10 | final override fun act(input: T, dependants: Int): DisposableSupplier {
11 | longProgress("Sinking ${this::class.simpleName}") {
12 | write(input)
13 | }
14 | return DisposableSupplier({}) {
15 | null
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/picnic_table.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/logomark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/models/CategoriesTest.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.models
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 | import org.trailcatalog.models.WayCategory.HIGHWAY
6 | import org.trailcatalog.models.WayCategory.PATH_FOOTWAY
7 | import org.trailcatalog.models.WayCategory.ROAD_MOTORWAY
8 |
9 | class CategoriesTest {
10 |
11 | @Test
12 | fun testIsParentOf() {
13 | assertThat(HIGHWAY.isParentOf(PATH_FOOTWAY)).isTrue()
14 | }
15 |
16 | @Test
17 | fun testNotIsParentOf() {
18 | assertThat(ROAD_MOTORWAY.isParentOf(PATH_FOOTWAY)).isFalse()
19 | }
20 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Trailcatalog
2 |
3 | Trailcatalog organizes OpenStreetMaps trail relations into a searchable catalog. It syncs to OSM
4 | every Monday and Thursday at 2am PST. https://trailcatalog.org
5 |
6 | ## Corgi
7 |
8 | This uses my JS framework [Corgi](https://github.com/aschleck/corgi/tree/main/js/corgi). I think
9 | it's cool and I hope you do too.
10 |
11 | ## S2
12 |
13 | This project depends on Google's
14 | [S2 Geometry Library](https://github.com/google/s2-geometry-library-java) and we host an S2 cell
15 | viewer at https://s2.trailcatalog.org. Left-click to toggle cells, right-click to see cell
16 | information.
17 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/bridge.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/js/map/common/math.test.ts:
--------------------------------------------------------------------------------
1 | import { tilesIntersect } from './math';
2 | import { TileId } from './types';
3 |
4 | test('finds intersecting tile', () => {
5 | const a = {
6 | x: 39,
7 | y: 97,
8 | zoom: 8,
9 | };
10 | const b = {
11 | x: 639,
12 | y: 1554,
13 | zoom: 12,
14 | };
15 | expect(tilesIntersect(a, b)).toBe(true);
16 | });
17 |
18 | test('finds tiles are disjoint', () => {
19 | const a = {
20 | x: 39,
21 | y: 98,
22 | zoom: 8,
23 | };
24 | const b = {
25 | x: 639,
26 | y: 1554,
27 | zoom: 12,
28 | };
29 | expect(tilesIntersect(a, b)).toBe(false);
30 | });
31 |
32 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/elevation/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "elevation",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "//java/org/trailcatalog:pg_support",
10 | "//java/org/trailcatalog/importers/common",
11 | "//java/org/trailcatalog/importers/elevation/tiff",
12 | "//java/org/trailcatalog/s2:s2-kt",
13 | "@com_google_geometry_s2//:s2",
14 | "@maven//:mil_nga_tiff",
15 | "@maven//:org_slf4j_slf4j_simple",
16 | ],
17 | )
18 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/collections/PMap.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.collections
2 |
3 | import com.google.common.reflect.TypeToken
4 |
5 | interface PMap, V> : PCollection>
6 |
7 | data class PEntry(val key: K, val values: List)
8 |
9 | fun , V : Any> createPMap(
10 | context: String,
11 | keyType: TypeToken,
12 | valueType: TypeToken,
13 | estimatedByteSize: Long,
14 | fn: (Emitter2) -> Unit): DisposableSupplier> {
15 | return createMmapPMap(context, keyType, valueType, estimatedByteSize, fn)
16 | }
17 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/elevation/tiff/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 |
5 | kt_jvm_library(
6 | name = "tiff",
7 | srcs = glob(["*.kt"]),
8 | deps = [
9 | "//java/org/trailcatalog/common",
10 | "//java/org/trailcatalog/importers/pipeline/io",
11 | "@com_google_geometry_s2//:s2",
12 | "@maven//:com_google_guava_guava",
13 | "@maven//:mil_nga_tiff",
14 | "@maven//:org_locationtech_proj4j_proj4j",
15 | "@maven//:org_locationtech_proj4j_proj4j_epsg",
16 | ],
17 | )
18 |
--------------------------------------------------------------------------------
/java/lat/trails/client/app.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Source+Sans+3&display=swap');
2 |
3 | @import "tailwindcss";
4 |
5 | @theme inline {
6 | --color-gray-800: #424242;
7 | --color-gray-900: #212121;
8 | --color-white-opaque-160: #ffffffa0;
9 | --color-white-opaque-250: #fffffffa;
10 | --font-sans: 'Source Sans 3', var(--font-sans);
11 |
12 | --animate-slide: slide 1.75s ease-in-out infinite;
13 | @keyframes slide {
14 | 0% {
15 | left: -33%;
16 | }
17 | 100% {
18 | left: 100%;
19 | }
20 | }
21 | }
22 |
23 | a:not(.no-underline):hover {
24 | @apply underline;
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/map/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "tc_ts_project")
4 |
5 | tc_ts_project(
6 | name = "map",
7 | deps = [
8 | "//java/org/trailcatalog/client/common",
9 | "//java/org/trailcatalog/client/data",
10 | "//java/org/trailcatalog/client/models",
11 | "//java/org/trailcatalog/client/workers:ts",
12 | "//java/org/trailcatalog/models:models-ts",
13 | "@dev_april_corgi//js/common",
14 | "@dev_april_corgi//js/corgi",
15 | "//js/map",
16 | "//js/map/rendering",
17 | ],
18 | )
19 |
20 |
--------------------------------------------------------------------------------
/trailcatalog/migrations/2_search.toml:
--------------------------------------------------------------------------------
1 | [[actions]]
2 | type = "custom"
3 |
4 | # Need to manually run this as postgres:
5 | # CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA pg_catalog;
6 | start = """
7 | CREATE EXTENSION IF NOT EXISTS btree_gist;
8 | CREATE INDEX IF NOT EXISTS search_boundaries_idx ON boundaries USING gist (epoch, name gist_trgm_ops(siglen=256));
9 | CREATE INDEX IF NOT EXISTS search_trails_idx ON trails USING gist (epoch, name gist_trgm_ops(siglen=256));
10 | """
11 |
12 | abort = """
13 | DROP INDEX IF EXISTS search_boundaries_idx;
14 | DROP INDEX IF EXISTS search_trails_idx;
15 | DROP EXTENSION IF EXISTS btree_gist;
16 | """
17 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/boundary_crumbs.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { Boundary } from './models/types';
4 |
5 | interface SimpleBoundary {
6 | id: bigint|string;
7 | name: string;
8 | type: number;
9 | }
10 |
11 | export function BoundaryCrumbs({boundaries}: {boundaries: SimpleBoundary[]}) {
12 | const crumbs =
13 | [...boundaries]
14 | .sort((a, b) => a.type - b.type)
15 | .map(b => {b.name})
16 | .flatMap(l => [
17 | l,
18 | ' › ',
19 | ]);
20 | crumbs.pop();
21 | return <>{crumbs}>;
22 | }
23 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/pipeline/Sum.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.collections.Emitter
5 | import org.trailcatalog.importers.pipeline.collections.PEntry
6 |
7 | class Sum : PTransformer, List>>, Int>(TypeToken.of(Int::class.java)) {
8 |
9 | override fun act(input: PEntry, List>>, emitter: Emitter) {
10 | var sum = 0
11 | for (p in input.values) {
12 | sum += p.first.sumOf { it }
13 | sum += p.second.sumOf { it }
14 | }
15 | emitter.emit(sum)
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/js/dino/select.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { Select as EmuSelect } from 'external/dev_april_corgi+/js/emu/select';
4 |
5 | export function Select(
6 | {className, ...props}: {
7 | className?: string,
8 | ref?: string,
9 | options: Array<{
10 | label: string;
11 | value: string;
12 | }>,
13 | } & corgi.Properties) {
14 | return <>
15 |
22 | >;
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/viewpoint.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/java/lat/trails/client/workers/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/lat/trails:internal"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | esbuild_binary(
6 | name = "collection_loader_worker",
7 | entry_point = "collection_loader.ts",
8 | deps = [
9 | ":workers",
10 | ],
11 | )
12 |
13 | tc_ts_project(
14 | name = "workers",
15 | deps = [
16 | "//:node_modules/@types/earcut",
17 | "//:node_modules/earcut",
18 | "//js/map",
19 | "//js/map/common",
20 | "//js/map/workers",
21 | "//java/org/trailcatalog/s2",
22 | "@dev_april_corgi//js/common",
23 | ],
24 | )
25 |
26 |
--------------------------------------------------------------------------------
/js/s2viewer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | S2 Viewer
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/Geometry.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.geometry.S2LatLng
4 | import com.google.common.geometry.S2Point
5 | import com.google.common.geometry.S2Polyline
6 | import org.trailcatalog.importers.pbf.LatLngE7
7 |
8 | fun e7ToS2(latE7: Int, lngE7: Int): S2Point {
9 | return S2LatLng.fromE7(latE7, lngE7).toPoint()
10 | }
11 |
12 | fun polylineToMeters(polyline: S2Polyline): Double {
13 | return polyline.arclengthAngle.radians() * 6371010.0
14 | }
15 |
16 | fun S2Point.toLatLngE7(): LatLngE7 {
17 | val ll = S2LatLng(this)
18 | return LatLngE7((ll.latDegrees() * 10_000_000).toInt(), (ll.lngDegrees() * 10_000_000).toInt())
19 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/barbecue.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@aspect_rules_js//js:defs.bzl", "js_library")
4 | load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
5 | load("@npm//:defs.bzl", "npm_link_all_packages")
6 | load("@rules_python//python:pip.bzl", "compile_pip_requirements")
7 |
8 | js_library(
9 | name = "package_json",
10 | srcs = ["package.json"],
11 | )
12 |
13 | ts_config(
14 | name = "tsconfig",
15 | src = "tsconfig.json",
16 | deps = [
17 | "//:node_modules/gts",
18 | ],
19 | )
20 |
21 | npm_link_all_packages(name = "node_modules")
22 |
23 | compile_pip_requirements(
24 | name = "requirements_lock",
25 | extra_args = ["--allow-unsafe"],
26 | )
27 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/LatLngRectE7.java:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf;
2 |
3 | import com.google.common.geometry.S2LatLng;
4 | import com.google.common.geometry.S2LatLngRect;
5 |
6 | public record LatLngRectE7(int lowLat, int lowLng, int highLat, int highLng) {
7 |
8 | public static LatLngRectE7 from(S2LatLngRect rect) {
9 | var low = rect.lo();
10 | var high = rect.hi();
11 | return new LatLngRectE7(low.lat().e7(), low.lng().e7(), high.lat().e7(), high.lng().e7());
12 | }
13 |
14 | public S2LatLngRect toS2LatLngRect() {
15 | return S2LatLngRect.fromPointPair(
16 | S2LatLng.fromE7(lowLat, lowLng),
17 | S2LatLng.fromE7(highLat, highLng));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/PStage.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import org.trailcatalog.importers.pipeline.collections.DisposableSupplier
4 |
5 | abstract class PStage {
6 |
7 | abstract fun act(input: I, dependants: Int): DisposableSupplier
8 |
9 | protected open fun estimateCount(): Long {
10 | return 0
11 | }
12 |
13 | protected open fun estimateElementBytes(): Long {
14 | return 0
15 | }
16 |
17 | protected open fun estimateRatio(): Double {
18 | return 0.0
19 | }
20 |
21 | protected fun estimateSize(inputSize: Long): Long {
22 | return (estimateRatio() * inputSize).toLong() + estimateCount() * estimateElementBytes()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/java/lat/trails/common/Database.kt:
--------------------------------------------------------------------------------
1 | package lat.trails.common
2 |
3 | import com.zaxxer.hikari.HikariConfig
4 | import com.zaxxer.hikari.HikariDataSource
5 | import org.trailcatalog.flags.FlagSpec
6 | import org.trailcatalog.flags.createFlag
7 |
8 | @FlagSpec("database_username_password")
9 | private val databaseUsernamePassword = createFlag("unset")
10 |
11 | @FlagSpec("database_url")
12 | private val databaseUrl = createFlag("unset")
13 |
14 | fun createConnection(): HikariDataSource {
15 | return HikariDataSource(HikariConfig().apply {
16 | jdbcUrl = "jdbc:" + databaseUrl.value
17 | val split = databaseUsernamePassword.value.split(':', limit = 2)
18 | username = split[0]
19 | password = split[1]
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/InvertMap.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.collections.Emitter2
5 | import org.trailcatalog.importers.pipeline.collections.PEntry
6 |
7 | class InvertMap, V : Comparable>(
8 | context: String, key: TypeToken, value: TypeToken) :
9 | PMapTransformer, V, K>(context, value, key) {
10 |
11 | override fun act(input: PEntry, emitter: Emitter2) {
12 | for (value in input.values) {
13 | emitter.emit(value, input.key)
14 | }
15 | }
16 |
17 | override fun estimateRatio(): Double {
18 | return 1.0
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/PgSupport.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog
2 |
3 | import com.zaxxer.hikari.HikariConfig
4 | import com.zaxxer.hikari.HikariDataSource
5 |
6 | fun createConnectionSource(maxSize: Int = -1, syncCommit: Boolean = true): HikariDataSource {
7 | return HikariDataSource(HikariConfig().apply {
8 | jdbcUrl = "jdbc:" + System.getenv("DATABASE_URL")!!
9 | val envUser = System.getenv("DATABASE_USERNAME_PASSWORD")!!
10 | val split = envUser.split(':', limit = 2)
11 | username = split[0]
12 | password = split[1]
13 |
14 | if (maxSize > 0) {
15 | maximumPoolSize = maxSize
16 | }
17 |
18 | if (!syncCommit) {
19 | connectionInitSql = "SET SESSION synchronous_commit TO OFF"
20 | }
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/java/lat/trails/client/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/lat/trails:internal"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | esbuild_binary(
6 | name = "client",
7 | entry_point = "app.tsx",
8 | deps = [
9 | ":css",
10 | ":ts",
11 | ],
12 | )
13 |
14 | tc_ts_project(
15 | name = "ts",
16 | deps = [
17 | "//:node_modules/@types/node",
18 | "//java/lat/trails/client/workers",
19 | "//js/map",
20 | "//js/map/layers",
21 | "//js/map/workers",
22 | "@dev_april_corgi//js/common",
23 | "@dev_april_corgi//js/corgi",
24 | "@dev_april_corgi//js/emu",
25 | "@dev_april_corgi//js/server:client",
26 | ],
27 | )
28 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/EpochTracker.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 |
5 | class EpochTracker(private val hikari: HikariDataSource) {
6 |
7 | @Volatile var epoch: Int = 0
8 |
9 | init {
10 | Thread {
11 | while (true) {
12 | hikari.connection.use {
13 | it.prepareStatement("SELECT epoch FROM active_epoch ORDER BY epoch DESC LIMIT 1").use {
14 | val results = it.executeQuery()
15 | if (results.next()) {
16 | epoch = results.getInt(1)
17 | } else {
18 | println("Unable to find an epoch")
19 | }
20 | }
21 | }
22 |
23 | Thread.sleep(10_000)
24 | }
25 | }.start()
26 | }
27 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/parking.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/js/map/rendering/texture_pool.ts:
--------------------------------------------------------------------------------
1 | import { Disposable } from 'external/dev_april_corgi+/js/common/disposable';
2 |
3 | import { Renderer } from './renderer';
4 |
5 | export class TexturePool extends Disposable {
6 |
7 | private readonly free: WebGLTexture[];
8 |
9 | constructor(private readonly renderer: Renderer) {
10 | super();
11 | this.free = [];
12 |
13 | this.registerDisposer(() => {
14 | for (const texture of this.free) {
15 | this.renderer.deleteTexture(texture);
16 | }
17 | });
18 | }
19 |
20 | acquire(): WebGLTexture {
21 | return this.free.pop() ?? this.renderer.createTexture();
22 | }
23 |
24 | release(texture: WebGLTexture): void {
25 | this.free.push(texture);
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/java/lat/trails/nginx.conf:
--------------------------------------------------------------------------------
1 | daemon off;
2 | pid /tmp/trails-nginx.pid;
3 |
4 | events {
5 | worker_connections 1024;
6 | }
7 |
8 | http {
9 | access_log /dev/stdout;
10 | error_log /dev/stderr;
11 | gzip on;
12 | index index.html;
13 |
14 | client_body_temp_path /tmp/nginx_body 1 2;
15 | proxy_temp_path /tmp/nginx_proxy 1 2;
16 |
17 | server {
18 | listen 7069;
19 | root bazel-bin/java/lat/trails/frontend/runner_/runner.runfiles/_main/java/lat/trails;
20 |
21 | default_type application/octet-stream;
22 | include /etc/nginx/mime.types;
23 |
24 | location /static/ {
25 | }
26 |
27 | location / {
28 | proxy_pass http://127.0.0.1:7050;
29 | proxy_set_header Host $http_host;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/production/containers/Containerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/debian:testing-slim
2 | RUN apt-get update \
3 | && apt-get install --no-install-recommends --yes \
4 | ca-certificates \
5 | curl \
6 | gnupg \
7 | nginx \
8 | openjdk-25-jre-headless \
9 | xz-utils \
10 | && apt-get clean \
11 | && curl -L https://nodejs.org/dist/v22.16.0/node-v22.16.0-linux-x64.tar.xz | tar -xvJf -
12 | ENV NODEJS_DIR /node-v22.16.0-linux-x64
13 | ARG NGINX_CONF
14 | COPY $NGINX_CONF /app/nginx.conf
15 | COPY serve.sh /app/serve.sh
16 | COPY frontend_pkg.tar ./
17 | RUN tar -xvf ./frontend_pkg.tar && rm ./frontend_pkg.tar
18 | RUN useradd app --create-home
19 | RUN chown -R app:app /var/lib/nginx
20 | USER app:app
21 | CMD /app/serve.sh
22 |
--------------------------------------------------------------------------------
/trailcatalog/migrations/1_enum_function.toml:
--------------------------------------------------------------------------------
1 | [[actions]]
2 | type = "custom"
3 |
4 | start = """
5 | CREATE OR REPLACE FUNCTION enumADescendsB(a integer, b integer, size integer) RETURNS boolean AS '
6 | SELECT
7 | a = b
8 | OR (a - 1) / size = b
9 | OR ((a - 1) / size - 1) / size = b
10 | OR (((a - 1) / size - 1) / size - 1) / size = b
11 | OR ((((a - 1) / size - 1) / size - 1) / size - 1) / size = b
12 | OR (((((a - 1) / size - 1) / size - 1) / size - 1) / size - 1) / size = b
13 | OR ((((((a - 1) / size - 1) / size - 1) / size - 1) / size - 1) / size - 1) / size = b
14 | '
15 | LANGUAGE SQL
16 | IMMUTABLE;
17 | """
18 |
19 | abort = """
20 | DROP FUNCTION IF EXISTS enumADescendsB;
21 | """
22 |
23 |
--------------------------------------------------------------------------------
/COPYRIGHT:
--------------------------------------------------------------------------------
1 | Trailcatalog, a website for hiking trails
2 | Copyright (C) 2022 April Schleck
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU Affero General Public License for more details.
13 |
14 | You should have received a copy of the GNU Affero General Public License
15 | along with this program. If not, see .
16 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/run.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | cd $(dirname "$0")
3 | cd ../../../
4 | bazelisk build java/org/trailcatalog:api_server java/org/trailcatalog/frontend:runner
5 | ibazel build --keep_going java/org/trailcatalog:api_server java/org/trailcatalog/frontend:runner &
6 | nginx \
7 | -c "$(pwd)/java/org/trailcatalog/nginx.conf" \
8 | -e /dev/stderr \
9 | -p "$(pwd)" &
10 | trap 'kill $(jobs -p)' EXIT
11 | BAZEL_BINDIR="." DEBUG=true ./bazel-bin/java/org/trailcatalog/frontend/runner_/runner &
12 | DATABASE_URL="postgresql://127.0.0.1:5432/trailcatalog?currentSchema=migration_3_faster" \
13 | DATABASE_USERNAME_PASSWORD="trailcatalog:trailcatalog" \
14 | DEFAULT_JVM_DEBUG_SUSPEND=n \
15 | ./bazel-bin/java/org/trailcatalog/api_server --debug=0.0.0.0:5005
16 |
--------------------------------------------------------------------------------
/js/s2viewer/nginx.conf:
--------------------------------------------------------------------------------
1 | daemon off;
2 | pid /tmp/trailcatalog-nginx.pid;
3 |
4 | events {
5 | worker_connections 1024;
6 | }
7 |
8 | http {
9 | access_log /dev/stdout;
10 | error_log /dev/stderr;
11 | gzip on;
12 | index index.html;
13 |
14 | client_body_temp_path /tmp/nginx_body 1 2;
15 | proxy_temp_path /tmp/nginx_proxy 1 2;
16 |
17 | server {
18 | listen 7069;
19 |
20 | default_type application/octet-stream;
21 | types {
22 | text/css css;
23 | text/html html;
24 | text/javascript js;
25 | text/xml xml;
26 | }
27 |
28 | location / {
29 | root bazel-bin/js/s2viewer;
30 | }
31 |
32 | location /tiles/ {
33 | root tiles;
34 | rewrite ^/tiles(.*)$ $1 break;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/third_party/deanc-esbuild-plugin-postcss/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Dean Clatworthy
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6 |
7 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/nginx.conf:
--------------------------------------------------------------------------------
1 | daemon off;
2 | pid /tmp/trailcatalog-nginx.pid;
3 |
4 | events {
5 | worker_connections 1024;
6 | }
7 |
8 | http {
9 | access_log /dev/stdout;
10 | error_log /dev/stderr;
11 | gzip on;
12 | index index.html;
13 |
14 | client_body_temp_path /tmp/nginx_body 1 2;
15 | proxy_temp_path /tmp/nginx_proxy 1 2;
16 |
17 | server {
18 | listen 7069;
19 | root bazel-bin/java/org/trailcatalog/api_server.runfiles/_main/java/org/trailcatalog;
20 |
21 | default_type application/octet-stream;
22 | include /etc/nginx/mime.types;
23 |
24 | location /api/ {
25 | proxy_pass http://127.0.0.1:7070;
26 | }
27 |
28 | location /static/ {
29 | }
30 |
31 | location / {
32 | proxy_pass http://127.0.0.1:7080;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DisambiguateTrailIds.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 |
8 | class DisambiguateTrailIds :
9 | PMapTransformer, String, Long>(
10 | "DisambiguateTrailIds", TypeToken.of(String::class.java), TypeToken.of(Long::class.java)) {
11 |
12 | override fun act(input: PEntry, emitter: Emitter2) {
13 | emitter.emit(input.key, input.values[0])
14 | for (i in 1 until input.values.size) {
15 | emitter.emit(input.key + "-${i + 1}", input.values[i])
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/production/files/network-ifs.nix:
--------------------------------------------------------------------------------
1 | {
2 | networking = {
3 | defaultGateway = { address = ""; interface = "eth0"; };
4 | defaultGateway6 = { address = ""; interface = "eth0"; };
5 | hostName = "violet";
6 | interfaces."eth0".ipv4.addresses = [ { address = ""; prefixLength = 26; } ];
7 | interfaces."eth0".ipv6.addresses = [ { address = ""; prefixLength = 64; } ];
8 | nameservers = [ "8.8.8.8" "8.8.4.4" ];
9 | useDHCP = false;
10 |
11 | firewall = {
12 | enable = true;
13 | allowedTCPPorts = [ 80 443 ];
14 | allowedUDPPorts = [ 41641 ];
15 | checkReversePath = "loose"; # To make Nix shut up about Tailscale's possible functionality
16 |
17 | interfaces."tailscale0".allowedTCPPorts = [ 22 5005 5432 ];
18 | };
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | esbuild_binary(
6 | name = "client",
7 | entry_point = "app.js",
8 | deps = [
9 | ":css",
10 | ":ts",
11 | ],
12 | )
13 |
14 | tc_ts_project(
15 | name = "ts",
16 | css_deps = [
17 | "//js/dino:css",
18 | ],
19 | deps = [
20 | "//java/org/trailcatalog/client/data",
21 | "//java/org/trailcatalog/client/map",
22 | "//js/dino",
23 | "//js/map",
24 | "//js/map/layers",
25 | "@dev_april_corgi//js/common",
26 | "@dev_april_corgi//js/corgi",
27 | "@dev_april_corgi//js/emu",
28 | "@dev_april_corgi//js/server:client",
29 | ],
30 | )
31 |
32 |
--------------------------------------------------------------------------------
/js/map/common/types.ts:
--------------------------------------------------------------------------------
1 | export interface Copyright {
2 | long: string;
3 | short?: string;
4 | url?: string;
5 | }
6 |
7 | export type Vec2 = Readonly<[number, number]>;
8 | export type Vec4 = Readonly<[number, number, number, number]>;
9 |
10 | export type Rect = Readonly<{
11 | low: Vec2;
12 | high: Vec2;
13 | }>;
14 |
15 | export type LatLng = Vec2 & {brand: 'LatLng'}; // in degrees, not radians
16 | export type LatLngRect = Rect & {brand: 'LatLngRect'}; // in degrees, not radians
17 |
18 | export type LatLngZoom = Readonly<{
19 | lat: number;
20 | lng: number;
21 | zoom: number;
22 | }>;
23 |
24 | export type RgbaU32 = number & {brand: 'RgbaU32'};
25 |
26 | export type TileId = Readonly<{
27 | x: number;
28 | y: number;
29 | zoom: number;
30 | }>;
31 |
32 | export type S2CellToken = string & {brand: 'S2CellToken'};
33 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/ExtractNodeWayPairs.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 |
8 | class ExtractNodeWayPairs : PMapTransformer, Long, Long>(
9 | "ExtractNodeWayPairs",
10 | TypeToken.of(Long::class.java),
11 | TypeToken.of(Long::class.java)) {
12 |
13 | override fun act(input: PEntry, emitter: Emitter2) {
14 | val way = input.values[0]
15 | for (nodeId in way.nodes.toSet()) {
16 | emitter.emit(nodeId, way.id)
17 | }
18 | }
19 |
20 | override fun estimateRatio(): Double {
21 | return 1.3
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/UniqueValues.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.collections.Emitter2
5 | import org.trailcatalog.importers.pipeline.collections.PEntry
6 |
7 | class UniqueValues, V : Any>(
8 | context: String, key: TypeToken, value: TypeToken) :
9 | PMapTransformer, K, V>(context, key, value) {
10 |
11 | override fun act(input: PEntry, emitter: Emitter2) {
12 | val seen = HashSet()
13 | for (value in input.values) {
14 | if (seen.contains(value)) {
15 | continue
16 | }
17 | seen.add(value)
18 | emitter.emit(input.key, value)
19 | }
20 | }
21 |
22 | override fun estimateRatio(): Double {
23 | return 1.0
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/models/ToTypeScript.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.models
2 |
3 | fun main() {
4 | println("export const ENUM_SIZE = ${ENUM_SIZE};")
5 |
6 | println("""
7 | export function aDescendsB(a: number, b: number): boolean {
8 | let cursor = a;
9 | while (b < cursor) {
10 | cursor = Math.floor((cursor - 1) / ENUM_SIZE);
11 | }
12 | return b === cursor;
13 | }
14 | """)
15 |
16 | println("export enum PointCategory {")
17 | for (c in PointCategory.values()) {
18 | println(" ${c} = ${c.id},")
19 | }
20 | println("}")
21 |
22 | println("export enum RelationCategory {")
23 | for (c in RelationCategory.values()) {
24 | println(" ${c} = ${c.id},")
25 | }
26 | println("}")
27 |
28 | println("export enum WayCategory {")
29 | for (c in WayCategory.values()) {
30 | println(" ${c} = ${c.id},")
31 | }
32 | println("}")
33 | }
34 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/boundary-filled.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/.bazelrc:
--------------------------------------------------------------------------------
1 | # Define a hermetic c++17 toolchain
2 | build --cxxopt='-std=c++17'
3 | build --host_cxxopt='-std=c++17'
4 |
5 | # Faster Python proto deserialization and serialization
6 | build --define=use_fast_cpp_protos=true
7 |
8 | # Use Java 21
9 | build --java_language_version=21
10 | build --java_runtime_version=21
11 | build --tool_java_language_version=21
12 | build --tool_java_runtime_version=21
13 |
14 | # Reduce cache invalidations by passing fewer environment variables
15 | build --incompatible_strict_action_env
16 |
17 | test --test_output=errors
18 |
19 | # Required by aspect_rules_ts
20 | common --@aspect_rules_ts//ts:skipLibCheck=honor_tsconfig
21 |
22 | # For corgi
23 | common --experimental_isolated_extension_usages
24 |
25 | # For overriding MODULE.bazel files
26 | common --registry=file:///%workspace%/build_defs/registry
27 | common --registry=https://bcr.bazel.build
28 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/frontend/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 | load("@aspect_rules_js//js:defs.bzl", "js_binary")
5 |
6 | js_binary(
7 | name = "runner",
8 | data = [
9 | ":frontend.js",
10 | ],
11 | entry_point = "frontend.js",
12 | )
13 |
14 | esbuild_binary(
15 | name = "frontend",
16 | entry_point = "server.js",
17 | minify = False,
18 | platform = "node",
19 | deps = [
20 | ":css",
21 | ":ts",
22 | ],
23 | )
24 |
25 | tc_ts_project(
26 | name = "ts",
27 | css_deps = [
28 | "//java/org/trailcatalog/client:css",
29 | ],
30 | deps = [
31 | "//java/org/trailcatalog/client:ts",
32 | "@dev_april_corgi//js/corgi",
33 | "@dev_april_corgi//js/server",
34 | ],
35 | )
36 |
--------------------------------------------------------------------------------
/production/containers/trails_lat_nginx.conf:
--------------------------------------------------------------------------------
1 | daemon off;
2 | error_log /dev/stderr;
3 | include /etc/nginx/modules-enabled/*.conf;
4 | pid /tmp/nginx.pid;
5 | worker_processes auto;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | access_log /dev/stdout;
13 | error_log /dev/stderr;
14 | etag on;
15 | gzip on;
16 | index index.html;
17 |
18 | default_type application/octet-stream;
19 | include /etc/nginx/mime.types;
20 | sendfile on;
21 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
22 | ssl_prefer_server_ciphers on;
23 | tcp_nopush on;
24 | types_hash_max_size 2048;
25 |
26 | server {
27 | listen 7059 default_server;
28 | root /app;
29 |
30 | location /static/ {
31 | }
32 |
33 | location / {
34 | proxy_pass http://127.0.0.1:7050;
35 | proxy_set_header Host $http_host;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/InnerJoinWays.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pbf.Way
5 | import org.trailcatalog.importers.pipeline.PMapTransformer
6 | import org.trailcatalog.importers.pipeline.collections.Emitter2
7 | import org.trailcatalog.importers.pipeline.collections.PEntry
8 |
9 | class InnerJoinWays: PMapTransformer, List>>, Long, Way>(
10 | "InnerJoinWays",
11 | TypeToken.of(Long::class.java),
12 | TypeToken.of(Way::class.java)) {
13 | override fun act(
14 | input: PEntry, List>>,
15 | emitter: Emitter2) {
16 | for (value in input.values) {
17 | if (value.first.isNotEmpty() && value.second.isNotEmpty()) {
18 | emitter.emit(input.key, value.first[0])
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/production/containers/trailcatalog_nginx.conf:
--------------------------------------------------------------------------------
1 | daemon off;
2 | error_log /dev/stderr;
3 | include /etc/nginx/modules-enabled/*.conf;
4 | pid /tmp/nginx.pid;
5 | worker_processes auto;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | access_log /dev/stdout;
13 | error_log /dev/stderr;
14 | etag on;
15 | gzip on;
16 | index index.html;
17 |
18 | default_type application/octet-stream;
19 | include /etc/nginx/mime.types;
20 | sendfile on;
21 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
22 | ssl_prefer_server_ciphers on;
23 | tcp_nopush on;
24 | types_hash_max_size 2048;
25 |
26 | server {
27 | listen 7060 default_server;
28 | root /app/static;
29 |
30 | location /static/ {
31 | rewrite ^/static(.*)$ $1 break;
32 | }
33 | }
34 |
35 | server {
36 | listen 7061 default_server;
37 | root /app/s2;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/common/data.ts:
--------------------------------------------------------------------------------
1 | import { LittleEndianView } from 'external/dev_april_corgi+/js/common/little_endian_view';
2 |
3 | import { LatLng, LatLngRect } from 'js/map/common/types';
4 |
5 | import { decodeBase64 } from './base64';
6 |
7 | export function latLngFromBase64E7(bytes: string): LatLng {
8 | const markerStream = new LittleEndianView(decodeBase64(bytes));
9 | return [
10 | markerStream.getInt32() / 10_000_000,
11 | markerStream.getInt32() / 10_000_000,
12 | ] as const as LatLng;
13 | }
14 |
15 | export function latLngRectFromBase64E7(bytes: string): LatLngRect {
16 | const boundStream = new LittleEndianView(decodeBase64(bytes));
17 | return {
18 | low: [boundStream.getInt32() / 10_000_000, boundStream.getInt32() / 10_000_000],
19 | high: [boundStream.getInt32() / 10_000_000, boundStream.getInt32() / 10_000_000],
20 | brand: 'LatLngRect' as const,
21 | } as LatLngRect;
22 | }
23 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DumpReadableTrailIds.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 | import org.trailcatalog.importers.pipeline.PSink
5 | import org.trailcatalog.importers.pipeline.collections.PMap
6 |
7 | class DumpReadableTrailIds(private val epoch: Int, private val hikari: HikariDataSource)
8 | : PSink>() {
9 |
10 | override fun write(input: PMap) {
11 | input.use {
12 | val stream =
13 | StringifyingInputStream(input) { trail, csv ->
14 | // numeric_id,readable_id,epoch
15 | csv.append(trail.values[0])
16 | csv.append(",\"")
17 | csv.append(trail.key)
18 | csv.append("\",")
19 | csv.append(epoch)
20 | csv.append("\n")
21 | }
22 | copyStreamToPg("trail_identifiers", stream, hikari)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/PSource.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import org.trailcatalog.importers.pipeline.collections.DisposableSupplier
4 | import org.trailcatalog.importers.pipeline.collections.PCollection
5 |
6 | abstract class PSource : PStage>() {
7 |
8 | abstract fun read(): Sequence
9 |
10 | final override fun act(input: Void?, dependants: Int): DisposableSupplier> {
11 | return DisposableSupplier({}) {
12 | val iterator = read().iterator()
13 |
14 | object : PCollection {
15 | override fun estimatedByteSize(): Long {
16 | return this@PSource.estimateCount() * this@PSource.estimateElementBytes()
17 | }
18 |
19 | override fun close() {}
20 |
21 | override fun hasNext() = iterator.hasNext()
22 |
23 | override fun next() = iterator.next()
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/production/files/trailcatalog_frontend.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -ex
4 |
5 | CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/home/april/frontend_key.json \
6 | gcloud auth print-access-token \
7 | | podman login -u oauth2accesstoken --password-stdin us-west1-docker.pkg.dev
8 |
9 | pg_pwd="$(CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/home/april/frontend_key.json \
10 | gcloud \
11 | secrets \
12 | versions \
13 | access \
14 | latest \
15 | --project trailcatalog \
16 | --secret db-authorization \
17 | --quiet \
18 | | sed 's/^[^:]*://' | tail -n 1)"
19 |
20 | podman run \
21 | --name trailcatalog-frontend \
22 | --pull always \
23 | --rm \
24 | --env DATABASE_URL="postgresql://localhost/trailcatalog?currentSchema=migration_3_faster" \
25 | --env DATABASE_USERNAME_PASSWORD="trailcatalog:${pg_pwd}" \
26 | --network host \
27 | us-west1-docker.pkg.dev/trailcatalog/containers/frontend:latest
28 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/GatherWayNodes.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 |
8 | class GatherWayNodes : PMapTransformer, List>>, Long, Node>(
9 | "GatherWayNodes", TypeToken.of(Long::class.java), TypeToken.of(Node::class.java)) {
10 |
11 | override fun act(input: PEntry, List>>, emitter: Emitter2) {
12 | for (value in input.values) {
13 | if (value.first.isEmpty()) {
14 | continue
15 | }
16 |
17 | val node = value.first[0]
18 | for (way in value.second) {
19 | emitter.emit(way, node)
20 | }
21 | }
22 | }
23 |
24 | override fun estimateRatio(): Double {
25 | return 1.0
26 | }
27 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/models/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("//build_defs:ts.bzl", "tc_ts_project")
4 | load("@rules_java//java:defs.bzl", "java_binary")
5 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
6 |
7 | kt_jvm_library(
8 | name = "models",
9 | srcs = glob(["*.kt"], exclude=["ToTypeScript.kt"]),
10 | )
11 |
12 | tc_ts_project(
13 | name = "models-ts",
14 | srcs = [":to_ts"],
15 | )
16 |
17 | genrule(
18 | name = "to_ts",
19 | cmd = "$(location :generator_to_ts) > \"$@\"",
20 | outs = ["categories.ts"],
21 | tools = [":generator_to_ts"],
22 | )
23 |
24 | java_binary(
25 | name = "generator_to_ts",
26 | main_class = "org.trailcatalog.models.ToTypeScriptKt",
27 | runtime_deps = [":generator_to_ts_lib"],
28 | )
29 |
30 | kt_jvm_library(
31 | name = "generator_to_ts_lib",
32 | srcs = ["ToTypeScript.kt"],
33 | deps = [
34 | ":models",
35 | ],
36 | )
37 |
38 |
--------------------------------------------------------------------------------
/trailcatalog/migrations/1_create_points.toml:
--------------------------------------------------------------------------------
1 | [[actions]]
2 | type = "create_table"
3 | name = "points"
4 | primary_key = ["id", "epoch"]
5 |
6 | [[actions.columns]]
7 | name = "id"
8 | nullable = false
9 | type = "BIGINT"
10 |
11 | [[actions.columns]]
12 | name = "epoch"
13 | nullable = false
14 | type = "INT"
15 |
16 | [[actions.columns]]
17 | name = "type"
18 | nullable = false
19 | type = "INT"
20 |
21 | [[actions.columns]]
22 | name = "cell"
23 | nullable = false
24 | type = "BIGINT"
25 |
26 | [[actions.columns]]
27 | name = "name"
28 | nullable = true
29 | type = "TEXT"
30 |
31 | [[actions.columns]]
32 | name = "marker_degrees_e7"
33 | nullable = false
34 | type = "BYTEA"
35 |
36 | [actions.partition_by]
37 | list = ["epoch"]
38 |
39 | [[actions]]
40 | type = "add_index"
41 | table = "points"
42 |
43 | [actions.index]
44 | name = "points_by_cell_epoch_idx"
45 | columns = ["cell", "epoch"]
46 | concurrently = false
47 |
48 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/common/ClosedIntRange.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.common
2 |
3 | import org.postgresql.util.PGobject
4 |
5 | data class ClosedIntRange(val low: Int, val high: Int) : PGobject() {
6 |
7 | init {
8 | type = "int4range"
9 | value = "[${low}, ${high}]"
10 | }
11 | }
12 |
13 | fun PGobject.toClosedIntRange(): ClosedIntRange {
14 | if (type != "int4range") {
15 | throw IllegalStateException("Cannot call toClosedIntRange on ${type}")
16 | }
17 | val items = value!!.split(",")
18 | if (items.size != 2) {
19 | throw IllegalStateException("Not a valid range: ${value}")
20 | }
21 | // Postgres normalizes [0, 1] to [0, 2) which works because this is on ints.
22 | if (items[0][0] != '[' || items[1][items[1].length - 1] != ')') {
23 | throw IllegalStateException("Range is not closed: ${value}")
24 | }
25 | return ClosedIntRange(
26 | items[0].substring(1).toInt(), items[1].substring(0, items[1].length - 1).toInt() - 1)
27 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/gts/tsconfig-google.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "baseUrl": ".",
6 | "esModuleInterop": true, // recommended by esbuild
7 | "isolatedModules": true, // recommended by esbuild
8 | "jsx": "react",
9 | "jsxFactory": "corgi.createVirtualElement",
10 | "jsxFragmentFactory": "corgi.Fragment",
11 | "lib": ["DOM", "DOM.Iterable", "es2022"],
12 | "module": "esnext",
13 | "moduleResolution": "bundler",
14 | "sourceMap": false, // we don't use it since we use esbuild
15 | "target": "es2022",
16 | "rootDir": ".",
17 | "paths": {
18 | // Setting this so LSP can find stuff in bazel-bin
19 | "*": ["./*", "./bazel-bin/*"]
20 | }
21 | },
22 | // Has to be set to workaround https://github.com/microsoft/TypeScript/issues/59036 but also
23 | // setting it to so LSP doesn't abort on too many files
24 | "exclude": [
25 | "bazel-trailcatalog/*"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/basemap/CreateTrailsTest.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 | import org.trailcatalog.importers.pbf.LatLngE7
6 | import org.trailcatalog.proto.WayGeometry
7 |
8 | class CreateTrailsTest {
9 |
10 | @Test
11 | fun testNestedRelationIsValid() {
12 | // 2024-06-16: popsicle
13 | assertThat(isValid(4813557)).isTrue()
14 | }
15 |
16 | @Test
17 | fun testSimpleRelationIsValid() {
18 | // 2024-06-16: two ways
19 | assertThat(isValid(4137055)).isTrue()
20 | }
21 |
22 | @Test
23 | fun testBrokenRelationIsBroken() {
24 | // 2024-06-16: I hate it
25 | assertThat(isValid(17639740)).isFalse()
26 | }
27 | }
28 |
29 | private fun isValid(id: Long): Boolean {
30 | val mapped = HashMap>()
31 | val ways = HashMap()
32 | val flattened = flattenWays(fetchRelation(id), mapped, ways, false)
33 | return flattened != null
34 | }
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DumpTrailsInBoundaries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 | import org.trailcatalog.importers.pipeline.PSink
5 | import org.trailcatalog.importers.pipeline.collections.PMap
6 |
7 | class DumpTrailsInBoundaries(private val epoch: Int, private val hikari: HikariDataSource)
8 | : PSink>() {
9 |
10 | override fun write(input: PMap) {
11 | input.use {
12 | val stream =
13 | StringifyingInputStream(input) { entry, csv ->
14 | val trail = entry.key
15 | for (boundary in entry.values) {
16 | // boundary_id,trail_id,epoch
17 | csv.append(boundary)
18 | csv.append(",")
19 | csv.append(trail)
20 | csv.append(",")
21 | csv.append(epoch)
22 | csv.append("\n")
23 | }
24 | }
25 | copyStreamToPg("trails_in_boundaries", stream, hikari)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/GatherTrailBoundaries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 |
8 | class GatherTrailBoundaries : PMapTransformer, List>>, Long, Boundary>(
9 | "GatherTrailBoundaries", TypeToken.of(Long::class.java), TypeToken.of(Boundary::class.java)) {
10 |
11 | override fun act(input: PEntry, List>>, emitter: Emitter2) {
12 | for (value in input.values) {
13 | if (value.first.isEmpty()) {
14 | continue
15 | }
16 |
17 | val boundary = value.first[0]
18 | for (trail in value.second) {
19 | emitter.emit(trail, boundary)
20 | }
21 | }
22 | }
23 |
24 | override fun estimateRatio(): Double {
25 | return 1.0
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/6afe187897bef7933475e6af374c893f4c84a293.tar.gz") { } }:
2 |
3 | let
4 | reshape = pkgs.reshape.overrideAttrs (old: rec {
5 | src = pkgs.fetchFromGitHub {
6 | owner = "aschleck";
7 | repo = "reshape";
8 | rev = "9bc5629d21fe72fe620d9e8561d9d6db37c0b8ee";
9 | hash = "sha256-FvCgtv3DJVkTMoSZ8a1pWVRlekPIaXB1wfAqS/Ws6z0=";
10 | };
11 | cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
12 | inherit src;
13 | hash = "sha256-yIiNk1bc0VpUBTQXuhv3Dye4CsI20qUr31Z2r14Qi2o=";
14 | };
15 | });
16 | in
17 | pkgs.mkShell {
18 | buildInputs = with pkgs; [
19 | bazel-watcher
20 | bazelisk
21 | buildifier
22 | gdal
23 | google-cloud-sdk
24 | imagemagick
25 | jdk21_headless
26 | neovim
27 | nginx
28 | nodePackages.npm
29 | nodePackages.pnpm
30 | nodejs-slim_20
31 | podman
32 | reshape
33 | ];
34 |
35 | shellHook = ''
36 | alias bazel=bazelisk
37 | alias vim=nvim
38 | '';
39 | }
40 |
--------------------------------------------------------------------------------
/py/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:private"])
2 |
3 | #load("@io_bazel_rules_docker//container:push.bzl", "container_push")
4 | #load("@io_bazel_rules_docker//python3:image.bzl", "py3_image")
5 | load("@rules_python//python:defs.bzl", "py_binary")
6 |
7 | py_binary(
8 | name = "svgs_to_atlas",
9 | srcs = ["svgs_to_atlas.py"],
10 | deps = [
11 | "@pip//reportlab",
12 | "@pip//svgutils",
13 | ],
14 | )
15 |
16 | py_binary(
17 | name = "planet_update",
18 | srcs = ["planet_update.py"],
19 | deps = [
20 | "@pip//osmium",
21 | ],
22 | )
23 |
24 | #py3_image(
25 | # name = "planet_update_container",
26 | # main = "planet_update.py",
27 | # srcs = ["planet_update.py"],
28 | # deps = [":planet_update"],
29 | #)
30 | #
31 | #container_push(
32 | # name = "planet_update_container_push",
33 | # format = "OCI",
34 | # image = ":planet_update_container",
35 | # registry = "us-west1-docker.pkg.dev",
36 | # repository = "trailcatalog/containers/planet_update",
37 | # tag = "latest",
38 | #)
39 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/scrapers/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_java//java:defs.bzl", "java_binary")
4 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
5 |
6 | java_binary(
7 | name = "decog",
8 | main_class = "org.trailcatalog.scrapers.DecogKt",
9 | runtime_deps = [":scrapers"],
10 | )
11 |
12 | java_binary(
13 | name = "scraper",
14 | main_class = "org.trailcatalog.scrapers.ScraperKt",
15 | runtime_deps = [":scrapers"],
16 | )
17 |
18 | kt_jvm_library(
19 | name = "scrapers",
20 | srcs = glob(["*.kt"]),
21 | runtime_deps = [
22 | "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8",
23 | ],
24 | deps = [
25 | "//java/org/trailcatalog:pg_support",
26 | "//java/org/trailcatalog/common",
27 | "//java/org/trailcatalog/importers/common",
28 | "//java/org/trailcatalog/importers/pipeline/io",
29 | "//java/org/trailcatalog/s2:s2-kt",
30 | "@maven//:com_google_code_gson_gson",
31 | "@maven//:com_squareup_okhttp3_okhttp",
32 | ],
33 | )
34 |
--------------------------------------------------------------------------------
/js/map/layers/skybox_layer.ts:
--------------------------------------------------------------------------------
1 | import { Layer } from '../layer';
2 | import { Planner } from '../rendering/planner';
3 | import { Drawable } from '../rendering/program';
4 | import { Renderer } from '../rendering/renderer';
5 |
6 | export class SkyboxLayer extends Layer {
7 |
8 | private readonly buffer: WebGLBuffer;
9 | private readonly drawable: Drawable;
10 |
11 | constructor(
12 | private readonly z: number,
13 | private readonly renderer: Renderer,
14 | ) {
15 | super([]);
16 | this.buffer = this.renderer.createDataBuffer(0);
17 | this.registerDisposer(() => { this.renderer.deleteBuffer(this.buffer); });
18 |
19 | const buffer = new ArrayBuffer(48 * 2 * 4);
20 | const drawables = [];
21 | this.drawable =
22 | this.renderer.skyboxProgram.plan(
23 | buffer,
24 | /* offset= */ 0,
25 | this.buffer,
26 | this.z).drawable;
27 | this.renderer.uploadData(buffer, buffer.byteLength, this.buffer);
28 | }
29 |
30 | override render(planner: Planner): void {
31 | planner.add([this.drawable]);
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/production/containers/build.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | set -ex
4 |
5 | cd "$(dirname $0)"
6 |
7 | bazelisk build \
8 | //java/lat/trails:frontend_pkg.tar \
9 | //java/org/trailcatalog:frontend_pkg.tar
10 |
11 | gcloud auth print-access-token | podman login -u oauth2accesstoken --password-stdin us-west1-docker.pkg.dev
12 |
13 | cp --no-preserve=mode ../../bazel-bin/java/lat/trails/frontend_pkg.tar .
14 | TMPDIR=/tmp buildah bud \
15 | -f Containerfile \
16 | --build-arg 'NGINX_CONF=trails_lat_nginx.conf' \
17 | --layers \
18 | --tag localhost/lat/trails/frontend:latest
19 | podman push localhost/lat/trails/frontend:latest us-west1-docker.pkg.dev/trailcatalog/containers/lat_trails_frontend:latest
20 |
21 | cp --no-preserve=mode ../../bazel-bin/java/org/trailcatalog/frontend_pkg.tar .
22 | TMPDIR=/tmp buildah bud \
23 | -f Containerfile \
24 | --build-arg 'NGINX_CONF=trailcatalog_nginx.conf' \
25 | --layers \
26 | --tag localhost/org/trailcatalog/frontend:latest
27 | podman push localhost/org/trailcatalog/frontend:latest us-west1-docker.pkg.dev/trailcatalog/containers/frontend:latest
28 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
4 | load("@rules_java//java:defs.bzl", "java_library")
5 |
6 | kt_jvm_library(
7 | name = "pbf",
8 | srcs = glob(["*.kt"]),
9 | deps = [
10 | ":pbf_java",
11 | "//java/org/trailcatalog/importers/pipeline",
12 | "//java/org/trailcatalog/importers/pipeline/collections",
13 | "//java/org/trailcatalog/models",
14 | "//java/org/trailcatalog/s2:s2-java",
15 | "//java/org/trailcatalog/s2:s2-kt",
16 | "//trailcatalog/proto:encoding_java_proto",
17 | "@maven//:com_google_guava_guava",
18 | "@maven//:com_wolt_osm_parallelpbf",
19 | "@maven//:org_apache_commons_commons_text",
20 | "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk7",
21 | ],
22 | )
23 |
24 | java_library(
25 | name = "pbf_java",
26 | srcs = glob(["*.java"]),
27 | deps = [
28 | "//trailcatalog/proto:encoding_java_proto",
29 | "@com_google_geometry_s2//:s2",
30 | ],
31 | )
32 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/app.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&family=Roboto:wght@400;500;600;700&family=Nunito+Sans:wght@400&display=swap');
2 |
3 | @import "tailwindcss";
4 |
5 | @theme inline {
6 | --color-black-opaque-20: #00000014;
7 | --color-tc-error-100: #ffeeee;
8 | --color-tc-error-200: #ffcccc;
9 | --color-tc-error-500: #e93636;
10 | --color-tc-error-900: #ac0000;
11 | --color-tc-gray-100: #f3f3f3;
12 | --color-tc-gray-200: #e2e2e2;
13 | --color-tc-gray-400: #737a80;
14 | --color-tc-gray-600: #3a3a3a;
15 | --color-tc-gray-700: #363636;
16 | --color-tc-gray-900: #222222;
17 | --color-tc-highlight-1: #ffe600;
18 | --color-tc-highlight-2: #9fe26b;
19 | --color-tc-highlight-3: #4f8d1f;
20 | --color-white-opaque-160: #ffffffa0;
21 |
22 | --font-header: 'Barlow', var(--font-sans);
23 | --font-input: 'Nunito Sans', var(--font-sans);
24 | --font-sans: 'Roboto', var(--font-sans);
25 | }
26 |
27 | a:not(.no-underline):hover {
28 | @apply underline;
29 | }
30 |
31 | th {
32 | font-weight: initial;
33 | text-align: initial;
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/trailcatalog/proto/encoding.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package trailcatalog;
4 |
5 | option java_multiple_files = true;
6 | option java_package = "org.trailcatalog.proto";
7 |
8 | message RelationSkeleton {
9 | repeated RelationSkeletonMember members = 1;
10 | }
11 |
12 | message RelationSkeletonMember {
13 | RelationMemberFunction function = 1;
14 |
15 | oneof value {
16 | uint64 node_id = 2;
17 | uint64 relation_id = 3;
18 | uint64 way_id = 4;
19 | }
20 | }
21 |
22 | message RelationGeometry {
23 | uint64 relation_id = 1;
24 | uint64 cell = 2;
25 | repeated RelationMember members = 3;
26 | }
27 |
28 | message RelationMember {
29 | RelationMemberFunction function = 1;
30 |
31 | oneof value {
32 | uint64 node_id = 2;
33 | RelationGeometry relation = 3;
34 | WayGeometry way = 4;
35 | }
36 | }
37 |
38 | message WayGeometry {
39 | uint64 way_id = 1;
40 | float down_meters = 2;
41 | float up_meters = 3;
42 | repeated int32 lat_lng_e7 = 4;
43 | }
44 |
45 | enum RelationMemberFunction {
46 | RELATION_MEMBER_FUNCTION_UNKNOWN = 0;
47 | INNER = 1;
48 | OUTER = 2;
49 | }
50 |
--------------------------------------------------------------------------------
/js/dino/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { FabricIcon, FabricIconName } from './fabric';
4 |
5 | export function Checkbox({label, ...props}: {
6 | label?: corgi.VElementOrPrimitive,
7 | } & corgi.InputProperties) {
8 | return <>
9 |
14 | >;
15 | }
16 |
17 | export function IconCheckbox({checked, className, icons, label, ...props}: {
18 | checked?: boolean,
19 | className?: string,
20 | icons: {checked: FabricIconName, unchecked: FabricIconName},
21 | label?: corgi.VElementOrPrimitive,
22 | } & corgi.InputProperties) {
23 | return <>
24 |
34 | >;
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/unit_selector_element.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { Radio } from 'js/dino/radio';
4 |
5 | import { getUnitSystem } from './common/formatters';
6 | import { State, UnitSelectorController } from './unit_selector_controller';
7 |
8 | export function UnitSelector({
9 | className,
10 | }: {
11 | className?: string,
12 | }, state: State|undefined, updateState: (newState: State) => void) {
13 | if (!state) {
14 | state = {system: getUnitSystem()};
15 | }
16 |
17 | return <>
18 |
28 |
37 |
38 | >;
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | filegroup(
4 | name = "static",
5 | srcs = glob(["images/**/*"]) + [
6 | ":js",
7 | ],
8 | )
9 |
10 | genrule(
11 | name = "js",
12 | srcs = [
13 | "//java/org/trailcatalog/client",
14 | "//java/org/trailcatalog/client/workers:data_fetcher_worker",
15 | "//js/map/workers:mbtile_loader_worker",
16 | "//js/map/workers:raster_loader_worker",
17 | "//js/map/workers:xyz_data_fetcher_worker",
18 | ],
19 | outs = [
20 | "client.css",
21 | "client.js",
22 | "client.js.map",
23 | "data_fetcher_worker.js",
24 | "data_fetcher_worker.js.map",
25 | "mbtile_loader_worker.js",
26 | "mbtile_loader_worker.js.map",
27 | "raster_loader_worker.js",
28 | "raster_loader_worker.js.map",
29 | "xyz_data_fetcher_worker.js",
30 | "xyz_data_fetcher_worker.js.map",
31 | ],
32 | cmd = "\n".join([
33 | "for i in $(SRCS); do",
34 | " cp \"$${i}\" \"$(@D)/$$(basename \"$${i}\")\"",
35 | "done",
36 | ]),
37 | )
38 |
39 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/common/types.ts:
--------------------------------------------------------------------------------
1 | import { S2LatLngRect, S2Polygon } from 'java/org/trailcatalog/s2';
2 | import { SimpleS2 } from 'java/org/trailcatalog/s2/SimpleS2';
3 | import { LatLngRect, Vec2 } from 'js/map/common/types';
4 |
5 | export type PixelRect = {
6 | low: Vec2;
7 | high: Vec2;
8 | } & {brand: 'PixelRect'};
9 |
10 | export function emptyPixelRect(): PixelRect {
11 | return {
12 | low: [0, 0],
13 | high: [0, 0],
14 | brand: 'PixelRect',
15 | };
16 | }
17 |
18 | // A LatLng is a pair of *degrees*.
19 | export type S2CellNumber = number & {brand: 'S2CellNumber'};
20 |
21 | export function s2LatLngRectToTc(rect: S2LatLngRect): LatLngRect {
22 | const lo = rect.lo();
23 | const hi = rect.hi();
24 | return {
25 | low: [lo.latDegrees(), lo.lngDegrees()],
26 | high: [hi.latDegrees(), hi.lngDegrees()],
27 | brand: 'LatLngRect',
28 | };
29 | }
30 |
31 | const EMPTY_POLYGON =
32 | SimpleS2.decodePolygon(Uint8Array.from([
33 | /* compressed= */ 4,
34 | /* level= */ 1,
35 | /* numLoops= */ 0,
36 | ]).buffer);
37 |
38 | export function emptyS2Polygon(): S2Polygon {
39 | return EMPTY_POLYGON;
40 | }
41 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/PSortedTransformer.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.collections.DisposableSupplier
5 | import org.trailcatalog.importers.pipeline.collections.Emitter
6 | import org.trailcatalog.importers.pipeline.collections.PCollection
7 | import org.trailcatalog.importers.pipeline.collections.PSortedList
8 | import org.trailcatalog.importers.pipeline.collections.createPSortedList
9 |
10 | abstract class PSortedTransformer>(
11 | private val type: TypeToken,
12 | ) : PStage, PSortedList>() {
13 |
14 | abstract fun act(input: I, emitter: Emitter)
15 |
16 | override fun act(input: PCollection, dependants: Int): DisposableSupplier> {
17 | val estimate =
18 | (estimateRatio() * input.estimatedByteSize()).toLong()
19 | + estimateCount() * estimateElementBytes()
20 | return createPSortedList(type, estimate) { emitter ->
21 | while (input.hasNext()) {
22 | act(input.next(), emitter)
23 | }
24 | input.close()
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/java/lat/trails/importers/BUILD:
--------------------------------------------------------------------------------
1 | load("@rules_java//java:defs.bzl", "java_binary")
2 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
3 |
4 | package(default_visibility = ["//java/org/trailcatalog:internal"])
5 |
6 | java_binary(
7 | name = "geo_json",
8 | main_class = "lat.trails.importers.GeoJsonKt",
9 | runtime_deps = [":importers"],
10 | )
11 |
12 | java_binary(
13 | name = "public_access",
14 | main_class = "lat.trails.importers.PublicAccessKt",
15 | runtime_deps = [":importers"],
16 | )
17 |
18 | kt_jvm_library(
19 | name = "importers",
20 | srcs = glob(["*.kt"]),
21 | deps = [
22 | "//java/lat/trails/common",
23 | "//java/org/trailcatalog/flags",
24 | "//java/org/trailcatalog/importers/basemap",
25 | "@com_google_geometry_s2//:s2",
26 | "@maven//:com_fasterxml_jackson_core_jackson_databind",
27 | "@maven//:mil_nga_geopackage_geopackage",
28 | "@maven//:mil_nga_sf",
29 | "@maven//:mil_nga_tiff",
30 | "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8",
31 | "@maven//:org_locationtech_proj4j_proj4j_epsg",
32 | "@maven//:org_slf4j_slf4j_simple",
33 | ],
34 | )
35 |
--------------------------------------------------------------------------------
/js/dino/input.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { Input as EmuInput } from 'external/dev_april_corgi+/js/emu/input';
4 |
5 | import { FabricIcon, FabricIconName } from './fabric';
6 |
7 | type InputProps = {
8 | className?: string,
9 | dense?: boolean,
10 | forceValue?: boolean,
11 | icon?: FabricIconName,
12 | inset?: corgi.VElementOrPrimitive,
13 | placeholder?: string,
14 | ref?: string,
15 | } & corgi.InputProperties;
16 |
17 | export function OutlinedInput({className, ...props}: {
18 | className?: string,
19 | } & InputProps) {
20 | return <>
21 |
27 | >;
28 | }
29 |
30 | function Input({className, dense, icon, ...props}: InputProps) {
31 | return <>
32 | : <>>}
37 | {...props}
38 | />
39 | >;
40 | }
41 |
--------------------------------------------------------------------------------
/java/lat/trails/frontend/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/lat/trails:internal"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 | load("@aspect_rules_js//js:defs.bzl", "js_binary")
5 |
6 | js_binary(
7 | name = "runner",
8 | data = [
9 | ":frontend.js",
10 | "//java/lat/trails/static",
11 | ],
12 | entry_point = "frontend.js",
13 | node_options = [
14 | "--enable-source-maps",
15 | ],
16 | )
17 |
18 | esbuild_binary(
19 | name = "frontend",
20 | entry_point = "server.ts",
21 | minify = False,
22 | platform = "node",
23 | deps = [
24 | ":css",
25 | ":ts",
26 | ],
27 | )
28 |
29 | tc_ts_project(
30 | name = "ts",
31 | css_deps = [
32 | "//java/lat/trails/client:css",
33 | ],
34 | deps = [
35 | "//:node_modules/@fastify/cookie",
36 | "//:node_modules/@fastify/reply-from",
37 | "//:node_modules/fastify",
38 | "//:node_modules/openid-client",
39 | "//:node_modules/postgres",
40 | "//java/lat/trails/client:ts",
41 | "@dev_april_corgi//js/common",
42 | "@dev_april_corgi//js/server",
43 | ],
44 | )
45 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/common/base64.ts:
--------------------------------------------------------------------------------
1 | // This and following based on https://developer.mozilla.org/en-US/docs/Glossary/Base64
2 | export function decodeBase64(base64: string): ArrayBuffer {
3 | const nInLen = base64.replace(/=+/, '').length;
4 | const nOutLen = nInLen / 4 * 3;
5 | const taBytes = new Uint8Array(nOutLen);
6 |
7 | for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
8 | nMod4 = nInIdx & 3;
9 | nUint24 |= b64ToUint6(base64.charCodeAt(nInIdx)) << 6 * (3 - nMod4);
10 | if (nMod4 === 3 || nInLen - nInIdx === 1) {
11 | for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
12 | taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
13 | }
14 | nUint24 = 0;
15 | }
16 | }
17 |
18 | return taBytes.buffer;
19 | }
20 |
21 | export function b64ToUint6(nChr: number): number {
22 | return nChr > 64 && nChr < 91 ?
23 | nChr - 65
24 | : nChr > 96 && nChr < 123 ?
25 | nChr - 71
26 | : nChr > 47 && nChr < 58 ?
27 | nChr + 4
28 | : nChr === 43 ?
29 | 62
30 | : nChr === 47 ?
31 | 63
32 | :
33 | 0;
34 | }
35 |
--------------------------------------------------------------------------------
/js/map/layer.ts:
--------------------------------------------------------------------------------
1 | import { S2LatLng, S2LatLngRect } from 'java/org/trailcatalog/s2';
2 | import { Disposable } from 'external/dev_april_corgi+/js/common/disposable';
3 | import { EventSpec } from 'external/dev_april_corgi+/js/corgi/events';
4 |
5 | import { Copyright } from './common/types';
6 | import { Planner } from './rendering/planner';
7 |
8 | export abstract class Layer extends Disposable {
9 |
10 | constructor(private readonly _copyrights: Copyright[]|undefined = undefined) {
11 | super();
12 | }
13 |
14 | get copyrights(): Copyright[] {
15 | return this._copyrights ?? [];
16 | }
17 |
18 | click(point: S2LatLng, px: [number, number], contextual: boolean, source: EventSource): boolean {
19 | return false;
20 | }
21 |
22 | hasNewData(): boolean {
23 | return false;
24 | }
25 |
26 | hover(point: S2LatLng, source: EventSource): boolean {
27 | return false;
28 | }
29 |
30 | loadingData(): boolean {
31 | return false;
32 | }
33 |
34 | render(planner: Planner, zoom: number): void {}
35 |
36 | viewportChanged(bounds: S2LatLngRect, zoom: number): void {}
37 | }
38 |
39 | export interface EventSource {
40 | trigger(spec: EventSpec, detail: D): void;
41 | }
42 |
--------------------------------------------------------------------------------
/java/lat/trails/client/citations_element.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | export function CitationsElement() {
4 | return <>
5 | Citations
6 |
7 | OpenStreetMap is open data, licensed under the
8 |
9 | Open Data Commons Open Database License
10 |
11 | (ODbL) by the OpenStreetMap Foundation (OSMF).
12 |
13 |
14 | MapTiler maps are licensed under the terms of their
15 | copyright.
16 |
17 |
18 | Elevation derived products are produced with a combination of the following datasets.
19 |
20 |
21 | -
22 | Copernicus WorldDEM-30 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018
23 | provided under COPERNICUS by the European Union and ESA; all rights reserved
24 |
25 | - NASADEM 1 arc second provided by NASA Earthdata
26 |
27 |
28 | >;
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DumpBoundariesInBoundaries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 | import org.trailcatalog.importers.pipeline.PSink
5 | import org.trailcatalog.importers.pipeline.collections.PMap
6 |
7 | class DumpBoundariesInBoundaries(private val epoch: Int, private val hikari: HikariDataSource)
8 | : PSink>() {
9 |
10 | override fun write(input: PMap) {
11 | input.use {
12 | val stream =
13 | StringifyingInputStream(input) { entry, csv ->
14 | val child = entry.key
15 | val seen = HashSet()
16 | for (parent in entry.values) {
17 | if (seen.contains(parent)) {
18 | continue
19 | }
20 | seen.add(parent)
21 |
22 | // child_id,parent_id,epoch
23 | csv.append(child)
24 | csv.append(",")
25 | csv.append(parent)
26 | csv.append(",")
27 | csv.append(epoch)
28 | csv.append("\n")
29 | }
30 | }
31 | copyStreamToPg("boundaries_in_boundaries", stream, hikari)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/GatherRelationWays.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 |
8 | /**
9 | * Given a way ID as a key and the pair of ,
10 | * emits a mapping from relation ID to .
11 | */
12 | class GatherRelationWays
13 | : PMapTransformer<
14 | PEntry, List>>,
15 | Long,
16 | Pair>(
17 | "GatherRelationWays",
18 | TypeToken.of(Long::class.java),
19 | object : TypeToken>() {}) {
20 |
21 | override fun act(
22 | input: PEntry, List>>,
23 | emitter: Emitter2>) {
24 | val wayId = input.key
25 | for (value in input.values) {
26 | if (value.second.isEmpty()) {
27 | continue
28 | }
29 |
30 | val geometry = value.second[0]
31 | for (relationId in value.first) {
32 | emitter.emit(relationId, Pair(wayId, geometry))
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/third_party/deanc-esbuild-plugin-postcss/index.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs/promises");
2 | const postcss = require("postcss");
3 | const util = require("util");
4 | const path = require("path");
5 |
6 | module.exports = (options = { plugins: [] }) => ({
7 | name: "postcss",
8 | setup: function (build) {
9 | build.onResolve(
10 | { filter: /.\.(css)$/, namespace: "file" },
11 | async (args) => {
12 | const sourceExt = path.extname(args.path);
13 | const sourceBaseName = path.basename(args.path, sourceExt);
14 | const sourceDir = path.dirname(args.path);
15 | const sourceFullPath = path.resolve(args.resolveDir, args.path);
16 | const tmpDir = path.resolve(process.cwd(), sourceDir);
17 | const tmpFilePath = path.resolve(tmpDir, `${sourceBaseName}.css`);
18 |
19 | const css = await fs.readFile(sourceFullPath);
20 |
21 | const result = await postcss(options.plugins).process(css, {
22 | from: sourceFullPath,
23 | to: tmpFilePath,
24 | });
25 |
26 | // Write result file
27 | await fs.writeFile(tmpFilePath, result.css);
28 |
29 | return {
30 | path: tmpFilePath,
31 | };
32 | }
33 | );
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DumpPathsInTrails.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 | import org.trailcatalog.importers.pbf.Way
5 | import org.trailcatalog.importers.pipeline.PSink
6 | import org.trailcatalog.importers.pipeline.collections.PCollection
7 | import org.trailcatalog.importers.pipeline.collections.PMap
8 |
9 | class DumpPathsInTrails(private val epoch: Int, private val hikari: HikariDataSource)
10 | : PSink>() {
11 |
12 | override fun write(input: PCollection) {
13 | input.use {
14 | val stream =
15 | StringifyingInputStream(input) { trail, csv ->
16 | val paths = HashSet()
17 | for (path in trail.paths) {
18 | paths.add(path.and(1L.inv()))
19 | }
20 | for (path in paths) {
21 | // path_id,trail_id,epoch
22 | csv.append(path)
23 | csv.append(",")
24 | csv.append(trail.relationId)
25 | csv.append(",")
26 | csv.append(epoch)
27 | csv.append("\n")
28 | }
29 | }
30 | copyStreamToPg("paths_in_trails", stream, hikari)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/production/files/provision.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | pg_pwd="$(CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/home/april/frontend_key.json \
5 | gcloud \
6 | secrets \
7 | versions \
8 | access \
9 | latest \
10 | --project trailcatalog \
11 | --secret db-authorization \
12 | --quiet \
13 | | sed 's/^[^:]*://' | tail -n 1)"
14 | echo "CREATE ROLE trailcatalog LOGIN ENCRYPTED PASSWORD '${pg_pwd}';" | sudo su postgres -c psql
15 | echo "CREATE DATABASE trailcatalog WITH OWNER trailcatalog;" | sudo su postgres -c psql
16 | echo "CREATE EXTENSION pg_trgm;" | sudo su postgres -c psql -- -d trailcatalog
17 |
18 | # run reshape
19 |
20 | pg_pwd="$(CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/home/april/frontend_key.json \
21 | gcloud \
22 | secrets \
23 | versions \
24 | access \
25 | latest \
26 | --project trailcatalog \
27 | --secret trails_lat \
28 | --quiet \
29 | | jq -r '.database_password')"
30 | echo "CREATE ROLE trails_lat LOGIN ENCRYPTED PASSWORD '${pg_pwd}';" | sudo su postgres -c psql
31 | echo "CREATE DATABASE trails_lat WITH OWNER trails_lat;" | sudo su postgres -c psql
32 | echo "CREATE EXTENSION pg_trgm;" | sudo su postgres -c psql -- -d trails_lat
33 |
34 | # run reshape
35 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/UpdateWayElevations.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pbf.Way
5 | import org.trailcatalog.importers.pipeline.PMapTransformer
6 | import org.trailcatalog.importers.pipeline.collections.Emitter2
7 | import org.trailcatalog.importers.pipeline.collections.PEntry
8 |
9 | class UpdateWayElevations
10 | : PMapTransformer, List>>, Long, Way>(
11 | "UpdateWayElevations",
12 | TypeToken.of(Long::class.java),
13 | TypeToken.of(Way::class.java),
14 | ) {
15 |
16 | override fun act(
17 | input: PEntry, List>>, emitter: Emitter2) {
18 | val way = input.values.stream().flatMap { it.first.stream() }.findFirst().orElse(null) ?: return
19 | val profile =
20 | input.values.stream().flatMap { it.second.stream() }.findFirst().orElse(null)
21 |
22 | val down = profile?.down?.toFloat() ?: Float.NaN
23 | val up = profile?.up?.toFloat() ?: Float.NaN
24 |
25 | emitter.emit(way.id, Way(way.id, way.hash, way.type, down, up, way.points))
26 | }
27 |
28 | override fun estimateRatio(): Double {
29 | return 0.5
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/PMapTransformer.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.collections.DisposableSupplier
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PCollection
7 | import org.trailcatalog.importers.pipeline.collections.PMap
8 | import org.trailcatalog.importers.pipeline.collections.createPMap
9 |
10 | abstract class PMapTransformer, V : Any>(
11 | private val context: String,
12 | private val keyType: TypeToken,
13 | private val valueType: TypeToken,
14 | ) : PStage, PMap>() {
15 |
16 | abstract fun act(input: I, emitter: Emitter2)
17 |
18 | override fun act(input: PCollection, dependants: Int): DisposableSupplier> {
19 | val estimate =
20 | (estimateRatio() * input.estimatedByteSize()).toLong()
21 | + estimateCount() * estimateElementBytes()
22 | return createPMap(context, keyType, valueType, estimate) { emitter ->
23 | while (input.hasNext()) {
24 | act(input.next(), emitter)
25 | }
26 | input.close()
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/js/map/copyright_dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { Button } from 'external/dev_april_corgi+/js/emu/button';
4 | import { ACTION } from 'external/dev_april_corgi+/js/emu/events';
5 |
6 | import { Copyright } from './common/types';
7 |
8 | export function CopyrightDialog({copyrights}: {copyrights: Copyright[]}) {
9 | const elements = [];
10 | for (const copyright of copyrights) {
11 | if (copyright.url) {
12 | elements.push({copyright.long});
13 | } else {
14 | elements.push(copyright.long);
15 | }
16 | }
17 | return <>
18 |
19 |
20 |
32 |
33 |
34 | {elements.map(c => - {c}
)}
35 |
36 |
37 | >;
38 | }
39 |
--------------------------------------------------------------------------------
/java/lat/trails/run.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | cd $(dirname "$0")
3 | cd ../../../
4 | bazelisk build \
5 | java/lat/trails:api_server \
6 | java/lat/trails/frontend:runner \
7 | java/lat/trails/static
8 | ibazel build \
9 | java/lat/trails:api_server \
10 | java/lat/trails/frontend:runner \
11 | java/lat/trails/static \
12 | &
13 | nginx \
14 | -c "$(pwd)/java/lat/trails/nginx.conf" \
15 | -e /dev/stderr \
16 | -p "$(pwd)" &
17 | trap 'kill $(jobs -p)' EXIT
18 |
19 | BAZEL_BINDIR="." \
20 | COOKIE_SECRET=" fjqip210 ! 34 12pfds*()! f ADFFSD fjko1~4!" \
21 | DEBUG=true \
22 | PGHOST="127.0.0.1" \
23 | PGPORT="5432" \
24 | PGUSER="trails_lat" \
25 | PGPASSWORD="trails_lat" \
26 | PGDATABASE="trails_lat" \
27 | OAUTH2_GOOGLE_CLIENT_ID="347014216307-7u1hp5dkr05p62q0r75b76rau37c6o9j.apps.googleusercontent.com" \
28 | OAUTH2_GOOGLE_SECRET="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" \
29 | ./bazel-bin/java/lat/trails/frontend/runner_/runner &
30 |
31 | DEFAULT_JVM_DEBUG_SUSPEND=n \
32 | ./bazel-bin/java/lat/trails/api_server \
33 | --debug=0.0.0.0:5005 \
34 | --database_username_password="trails_lat:trails_lat" \
35 | --database_url="postgresql://127.0.0.1:5432/trails_lat?currentSchema=migration_1_data"
36 |
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "//": "stack-chain is an undeclared dep of cls-hooked, yikes!",
3 | "type": "module",
4 | "devDependencies": {
5 | "@tailwindcss/node": "^4.1.8",
6 | "@tailwindcss/oxide": "^4.1.8",
7 | "@types/earcut": "^2.1.4",
8 | "@types/jest": "^29.5.14",
9 | "@types/node": "^20.17.57",
10 | "jest-cli": "^29.7.0",
11 | "jest-environment-jsdom": "^29.7.0",
12 | "jest-junit": "^16.0.0",
13 | "lightningcss": "^1.30.1",
14 | "postcss": "^8.5.4",
15 | "stack-chain": "^2.0.0",
16 | "supports-color": "^9.4.0",
17 | "tailwindcss": "^4.1.8"
18 | },
19 | "dependencies": {
20 | "@fastify/cookie": "^11.0.2",
21 | "@fastify/reply-from": "^11.0.2",
22 | "@fastify/request-context": "^6.2.0",
23 | "@mapbox/tiny-sdf": "^2.0.6",
24 | "earcut": "^2.2.4",
25 | "fastify": "^5.3.3",
26 | "geotiff": "https://github.com/aschleck/geotiff.js/archive/32c24abbdacaf563ccc23391dafffd3d7f276c67.tar.gz",
27 | "grapheme-splitter": "^1.0.4",
28 | "gts": "^6.0.2",
29 | "openid-client": "^5.7.1",
30 | "postgres": "^3.4.7",
31 | "typescript": "^5.8.3"
32 | },
33 | "engines": {
34 | "node": ">=14.0.0"
35 | },
36 | "pnpm": {
37 | "onlyBuiltDependencies": [
38 | "fsevents"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/go_to_trail_element.tsx:
--------------------------------------------------------------------------------
1 | import { Future } from 'external/dev_april_corgi+/js/common/futures';
2 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
3 | import { redirectTo } from 'external/dev_april_corgi+/js/server/ssr_aware';
4 |
5 | import { Trail } from './models/types';
6 |
7 | import { fetchData } from './data';
8 | import { trailFromRaw } from './trails';
9 |
10 | interface State {
11 | trail: Future;
12 | }
13 |
14 | export function GoToTrailElement({trailId, parameters}: {
15 | trailId: string;
16 | parameters: {[key: string]: string};
17 | }, inState: State|undefined, updateState: (newState: State) => void) {
18 | if (!inState) {
19 | inState = {
20 | trail: fetchData('trail', {trail_id: {numeric: trailId}}).then(trailFromRaw),
21 | };
22 | }
23 | const state = inState;
24 |
25 | if (state.trail.finished) {
26 | const trail = state.trail.value();
27 | redirectTo(`/trail/${trail.readableId}`);
28 | return <>
29 |
30 | Click here if you are not automatically redirected
31 |
32 | >;
33 | } else {
34 | state.trail.then(() => {
35 | updateState(state);
36 | });
37 |
38 | return <>Redirecting...>;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/GeoTiff.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.common
2 |
3 | // https://docs.opengeospatial.org/is/19-008r4/19-008r4.html#_requirements_class_geodeticcrsgeokey
4 |
5 | enum class GeoTiffTagType(val id: UShort) {
6 | // http://geotiff.maptools.org/spec/geotiff2.7.html
7 | GTModelTypeGeoKey(1024.toUShort()),
8 | GTRasterTypeGeoKey(1025.toUShort()),
9 | GeodeticCRSGeoKey(2048.toUShort()),
10 | GeogAngularUnitsGeoKey(2054.toUShort()),
11 | ProjectedCSTypeGeoKey(3072.toUShort()),
12 | ProjLinearUnitsGeoKey(3076.toUShort()),
13 | }
14 |
15 | enum class GeoTiffAngularUnits(val id: UShort) {
16 | // http://geotiff.maptools.org/spec/geotiff6.html#6.3.1.4
17 | AngularDegree(9102.toUShort()),
18 | }
19 |
20 | enum class GeoTiffLinearUnits(val id: UShort) {
21 | // http://geotiff.maptools.org/spec/geotiff6.html#6.3.1.3
22 | LinearMeter(9001.toUShort()),
23 | }
24 |
25 | enum class GeoTiffModelType(val id: UShort) {
26 | // http://geotiff.maptools.org/spec/geotiff6.html#6.3.1.1
27 | ModelTypeProjected(1.toUShort()),
28 | ModelTypeGeographic(2.toUShort()),
29 | ModelTypeGeocentric(3.toUShort()),
30 | }
31 |
32 | enum class GeoTiffRasterSpace(val id: UShort) {
33 | // http://geotiff.maptools.org/spec/geotiff6.html#6.3.1.2
34 | RasterPixelIsArea(1.toUShort()),
35 | RasterPixelIsPoint(2.toUShort()),
36 | }
37 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/elevation/contour/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_java//java:defs.bzl", "java_binary", "java_library")
4 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
5 |
6 | java_binary(
7 | name = "hillshader",
8 | main_class = "org.trailcatalog.importers.elevation.contour.HillshaderKt",
9 | runtime_deps = [":contour"],
10 | )
11 |
12 | java_binary(
13 | name = "tile_contours",
14 | main_class = "org.trailcatalog.importers.elevation.contour.TileContoursKt",
15 | runtime_deps = [":contour"],
16 | )
17 |
18 | kt_jvm_library(
19 | name = "contour",
20 | srcs = glob(["*.kt"]),
21 | data = [
22 | "//java/org/trailcatalog/scrapers:decog",
23 | ],
24 | runtime_deps = [
25 | "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8",
26 | "@maven//:org_sejda_imageio_webp_imageio",
27 | "@maven//:org_xerial_sqlite_jdbc",
28 | ],
29 | deps = [
30 | "//java/org/trailcatalog/flags",
31 | "//java/org/trailcatalog/importers/elevation",
32 | "//third_party/mapbox-vector-tiles:vector_tile_java_proto",
33 | "@com_google_geometry_s2//:s2",
34 | "@maven//:com_google_code_gson_gson",
35 | "@maven//:com_squareup_okhttp3_okhttp",
36 | "@maven//:org_wololo_flatgeobuf",
37 | ],
38 | )
39 |
--------------------------------------------------------------------------------
/js/dino/radio.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | export function Radio({className, name, options, value, ...props}: {
4 | className?: string,
5 | name: string,
6 | options: Array<{
7 | label: string;
8 | value: string;
9 | }>,
10 | value: string,
11 | } & corgi.Properties) {
12 | return <>
13 |
20 | {options.map(o =>
21 |
41 | )}
42 |
43 | >;
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/java/lat/trails/client/app.tsx:
--------------------------------------------------------------------------------
1 | import { checkExhaustive, checkExists } from 'external/dev_april_corgi+/js/common/asserts';
2 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
3 |
4 | import { CitationsElement } from './citations_element';
5 | import { RouteController, State } from './route_controller';
6 | import { OverviewElement } from './overview_element';
7 |
8 | import './app.css';
9 |
10 | // TODO: assert little endian
11 |
12 | export function App(props: {}, state: State|undefined, updateState: (newState: State) => void) {
13 | if (!state) {
14 | state = RouteController.getInitialState();
15 | }
16 |
17 | let route;
18 | if (state.active.kind === 'citations') {
19 | route = ;
20 | } else if (state.active.kind === 'overview') {
21 | route = ;
22 | } else {
23 | checkExhaustive(state.active);
24 | }
25 |
26 | return <>
27 |
37 | {route}
38 |
39 | >;
40 | }
41 |
42 | if (process.env.CORGI_FOR_BROWSER) {
43 | corgi.hydrateElement(checkExists(document.getElementById('root')), );
44 | }
45 |
--------------------------------------------------------------------------------
/production/pulumi/__main__.py:
--------------------------------------------------------------------------------
1 | from pulumi import Output, ResourceOptions
2 | from pulumi_gcp import artifactregistry, compute, projects, secretmanager, serviceaccount
3 |
4 | db_authorization = secretmanager.Secret(
5 | "db-authorization",
6 | secret_id="db-authorization",
7 | replication=secretmanager.SecretReplicationArgs(automatic=True),
8 | )
9 |
10 | frontend_account = serviceaccount.Account(
11 | "frontend",
12 | account_id="frontend",
13 | )
14 |
15 | importer_account = serviceaccount.Account(
16 | "importer",
17 | account_id="importer",
18 | )
19 |
20 | projects.IAMBinding(
21 | "artifact-access",
22 | project="trailcatalog",
23 | role="roles/artifactregistry.reader",
24 | members=[
25 | frontend_account.email.apply(lambda name: f"serviceAccount:{name}"),
26 | importer_account.email.apply(lambda name: f"serviceAccount:{name}"),
27 | ],
28 | )
29 |
30 | projects.IAMBinding(
31 | "secrets-access",
32 | project="trailcatalog",
33 | role="roles/secretmanager.secretAccessor",
34 | members=[
35 | frontend_account.email.apply(lambda name: f"serviceAccount:{name}"),
36 | importer_account.email.apply(lambda name: f"serviceAccount:{name}"),
37 | ],
38 | )
39 |
40 | registry = artifactregistry.Repository(
41 | "containers",
42 | repository_id="containers",
43 | format="DOCKER",
44 | location="us-west1",
45 | )
46 |
47 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/frontend/server.ts:
--------------------------------------------------------------------------------
1 | import process from 'process';
2 |
3 | import { serve } from 'external/dev_april_corgi+/js/server/server';
4 |
5 | import { App } from '../client/app';
6 |
7 | function page(content: string, title: string, initialData: string): string {
8 | return `
9 |
10 |
11 |
12 |
13 | ${title}
14 |
15 |
18 |
19 |
20 |
21 |
22 | ${content}
23 |
24 |
25 |
26 |
30 |
31 | `;
32 | }
33 |
34 | (async () => {
35 | await serve(App as any, page, {
36 | defaultTitle: 'Trailcatalog',
37 | port: 7080,
38 | });
39 | })();
40 |
41 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/MakeWayGeometries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 |
8 | class MakeWayGeometries
9 | : PMapTransformer, List>>, Long, Way>(
10 | "MakeWayGeometries",
11 | TypeToken.of(Long::class.java),
12 | TypeToken.of(Way::class.java),
13 | ) {
14 |
15 | override fun act(
16 | input: PEntry, List>>, emitter: Emitter2) {
17 | val way = input.values.stream().flatMap { it.first.stream() }.findFirst().orElse(null) ?: return
18 |
19 | val mapped = HashMap()
20 | for (value in input.values) {
21 | for (node in value.second) {
22 | mapped[node.id] = node.latLng
23 | }
24 | }
25 |
26 | val geometry = ArrayList()
27 | var hash = 0
28 | for (id in way.nodes) {
29 | val node = mapped[id] ?: return
30 | hash = 31 * hash + node.lat
31 | hash = 31 * hash + node.lng
32 | geometry.add(node)
33 | }
34 | emitter.emit(way.id, Way(way.id, hash, way.type, Float.NaN, Float.NaN, geometry))
35 | }
36 |
37 | override fun estimateRatio(): Double {
38 | return 0.5
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DumpBoundaries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 | import org.apache.commons.text.StringEscapeUtils
5 | import org.postgresql.copy.CopyManager
6 | import org.postgresql.jdbc.PgConnection
7 | import org.trailcatalog.importers.pipeline.PSink
8 | import org.trailcatalog.importers.pipeline.collections.PCollection
9 | import java.io.InputStream
10 |
11 | class DumpBoundaries(private val epoch: Int, private val hikari: HikariDataSource)
12 | : PSink>() {
13 |
14 | override fun write(input: PCollection) {
15 | input.use {
16 | val stream =
17 | StringifyingInputStream(input) { boundary, csv ->
18 | // id,epoch,type,cell,name,s2_polygon,source_relation
19 | csv.append(boundary.id)
20 | csv.append(",")
21 | csv.append(epoch)
22 | csv.append(",")
23 | csv.append(boundary.type)
24 | csv.append(",")
25 | csv.append(boundary.cell)
26 | csv.append(",")
27 | csv.append(StringEscapeUtils.escapeCsv(boundary.name))
28 | csv.append(",")
29 | appendByteArray(boundary.s2Polygon, csv)
30 | csv.append(",")
31 | csv.append(boundary.id)
32 | csv.append("\n")
33 | }
34 | copyStreamToPg("boundaries", stream, hikari)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/js/dino/button.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import { Button as EmuButton } from 'external/dev_april_corgi+/js/emu/button';
4 | import { FabricIcon, FabricIconName } from './fabric';
5 |
6 | type ButtonProps = {
7 | ariaLabel?: string,
8 | className?: string,
9 | dense?: boolean,
10 | icon?: FabricIconName,
11 | label?: string,
12 | } & corgi.Properties;
13 |
14 | export function FlatButton({className, ...props}: ButtonProps) {
15 | return <>
16 |
19 | >;
20 | }
21 |
22 | export function OutlinedButton({className, ...props}: ButtonProps) {
23 | return <>
24 |
31 | >;
32 | }
33 |
34 | export function Button({className, dense, icon, label, ...props}: ButtonProps) {
35 | return <>
36 |
44 | {icon ? <>{' '}> : ''}
45 | {label ? {label} : ''}
46 |
47 | >;
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/common/weather.ts:
--------------------------------------------------------------------------------
1 | import { checkExhaustive } from 'external/dev_april_corgi+/js/common/asserts';
2 | import { FabricIconName } from 'js/dino/fabric';
3 |
4 | export const WEATHER_LABEL_TO_ICON: {[k: string]: FabricIconName} = {
5 | 'Unknown': 'Checkbox',
6 | 'Clear': 'Sunny',
7 | 'Partly cloudy': 'PartlySunnyDay',
8 | 'Cloudy': 'Cloudy',
9 | 'Fog': 'Fog',
10 | 'Rain': 'Rain',
11 | 'Snow': 'Snow',
12 | 'Rain showers': 'Rain',
13 | 'Snow showers': 'LightSnow',
14 | 'Thunderstorms': 'Thunderstorms',
15 | } as const;
16 |
17 | export function formatWeatherCode(code: number): {
18 | icon: FabricIconName,
19 | label: keyof typeof WEATHER_LABEL_TO_ICON,
20 | } {
21 | let label;
22 | if (0 <= code && code <= 1) {
23 | label = 'Clear';
24 | } else if (code === 2) {
25 | label = 'Partly cloudy';
26 | } else if (code === 3) {
27 | label = 'Cloudy';
28 | } else if (45 <= code && code <= 48) {
29 | label = 'Fog';
30 | } else if (51 <= code && code <= 65) {
31 | label = 'Rain';
32 | } else if (71 <= code && code <= 77) {
33 | label = 'Snow';
34 | } else if (80 <= code && code <= 82) {
35 | label = 'Rain showers';
36 | } else if (85 <= code && code <= 86) {
37 | label = 'Snow showers';
38 | } else if (95 <= code && code <= 99) {
39 | label = 'Thunderstorms';
40 | } else {
41 | label = 'Unknown';
42 | }
43 | return {
44 | icon: WEATHER_LABEL_TO_ICON[label],
45 | label,
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/unit_selector_controller.ts:
--------------------------------------------------------------------------------
1 | import { checkExists } from 'external/dev_april_corgi+/js/common/asserts';
2 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
3 | import { Controller, Response } from 'external/dev_april_corgi+/js/corgi/controller';
4 | import { CorgiEvent } from 'external/dev_april_corgi+/js/corgi/events';
5 | import { HistoryService } from 'external/dev_april_corgi+/js/corgi/history/history_service';
6 | import { ACTION } from 'external/dev_april_corgi+/js/emu/events';
7 |
8 | import { setUnitSystem, UnitSystem } from './common/formatters';
9 |
10 | export interface State {
11 | system: UnitSystem;
12 | }
13 |
14 | type Deps = typeof UnitSelectorController.deps;
15 |
16 | export class UnitSelectorController extends Controller<{}, Deps, HTMLElement, State> {
17 |
18 | static deps() {
19 | return {
20 | services: {
21 | history: HistoryService,
22 | },
23 | };
24 | }
25 |
26 | private readonly history: HistoryService;
27 |
28 | constructor(response: Response) {
29 | super(response);
30 | this.history = response.deps.services.history;
31 | }
32 |
33 | select(e: CorgiEvent): void {
34 | const value = (e.targetElement.element() as HTMLInputElement).value;
35 | setUnitSystem(value as UnitSystem);
36 | corgi.vdomCaching.disable();
37 | this.history.reload().then(() => {
38 | corgi.vdomCaching.enable();
39 | });
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/common/ProgressBar.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.common
2 |
3 | import com.google.common.util.concurrent.AtomicDouble
4 | import java.io.Closeable
5 |
6 | class ProgressBar(
7 | private val action: String,
8 | private val units: String,
9 | private val limit: Number = -1.0) : Closeable {
10 |
11 | @Volatile private var active = true
12 | private val count = AtomicDouble(0.0)
13 | private val start = System.currentTimeMillis() - 1
14 |
15 | init {
16 | print(message())
17 | Thread {
18 | var running = true
19 | while (running) {
20 | Thread.sleep(500)
21 |
22 | if (active) {
23 | print("\r${message()}")
24 | } else {
25 | running = false
26 | }
27 | }
28 | }.start()
29 | }
30 |
31 | override fun close() {
32 | active = false
33 | println("\rFinished ${message()} in ${(System.currentTimeMillis() - start) / 1000.0} seconds")
34 | }
35 |
36 | fun increment() {
37 | incrementBy(1)
38 | }
39 |
40 | fun incrementBy(amount: Number) {
41 | count.addAndGet(amount.toDouble())
42 | }
43 |
44 | private fun message(): String {
45 | val c = count.get()
46 | val roundCount = "%.2f".format(c)
47 | val progress = if (limit.toDouble() >= 0) "${roundCount}/${limit}" else roundCount
48 | return "${action}: ${progress} ${units} " +
49 | "(%.2f ${units}/second)".format(c * 1000.0 / (System.currentTimeMillis() - start))
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/basemap/BUILD.bazel:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/org/trailcatalog:internal"])
2 |
3 | load("@rules_java//java:defs.bzl", "java_binary")
4 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test")
5 |
6 | kt_jvm_test(
7 | name = "basemap",
8 | srcs = glob(["*.kt"]),
9 | test_class = "org.trailcatalog.importers.basemap.CreateTrailsTest",
10 | deps = [
11 | ":relation_orientation_checker_lib",
12 | "//java/org/trailcatalog/importers/basemap",
13 | "//trailcatalog/proto:encoding_java_proto",
14 | "@maven//:com_google_truth_truth",
15 | "@maven//:org_junit_jupiter_junit_jupiter",
16 | ],
17 | )
18 |
19 | java_binary(
20 | name = "relation_orientation_checker",
21 | main_class = "org.trailcatalog.importers.basemap.RelationOrientationCheckerKt",
22 | runtime_deps = [":relation_orientation_checker_lib"],
23 | )
24 |
25 | kt_jvm_library(
26 | name = "relation_orientation_checker_lib",
27 | srcs = ["RelationOrientationChecker.kt"],
28 | runtime_deps = [
29 | "@maven//:org_jetbrains_kotlin_kotlin_stdlib_jdk8",
30 | ],
31 | deps = [
32 | "//java/org/trailcatalog/common",
33 | "//java/org/trailcatalog/importers/basemap",
34 | "//java/org/trailcatalog/importers/pbf",
35 | "//java/org/trailcatalog/s2:s2-kt",
36 | "//trailcatalog/proto:encoding_java_proto",
37 | "@maven//:de_westnordost_osmapi",
38 | ],
39 | )
40 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/trail.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/DumpPathElevations.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.zaxxer.hikari.HikariDataSource
4 | import org.trailcatalog.importers.pipeline.PSink
5 | import org.trailcatalog.importers.pipeline.collections.PCollection
6 | import org.trailcatalog.importers.pipeline.collections.PMap
7 | import java.nio.ByteBuffer
8 | import java.nio.ByteOrder
9 |
10 | class DumpPathElevations(private val epoch: Int, private val hikari: HikariDataSource)
11 | : PSink>() {
12 |
13 | override fun write(input: PMap) {
14 | input.use {
15 | val stream =
16 | StringifyingInputStream(input) { pair, csv ->
17 | val profile = pair.values[0]
18 | // id,epoch,down_meters,up_meters,height_samples_10m_meters
19 | csv.append(2 * profile.id)
20 | csv.append(",")
21 | csv.append(epoch)
22 | csv.append(",")
23 | csv.append(profile.down)
24 | csv.append(",")
25 | csv.append(profile.up)
26 | csv.append(",")
27 | val samples =
28 | ByteBuffer.allocate(4 * profile.profile.size).order(ByteOrder.LITTLE_ENDIAN)
29 | for (i in 0 until profile.profile.size) {
30 | samples.putFloat(profile.profile[i])
31 | }
32 | appendByteArray(samples.array(), csv)
33 | csv.append("\n")
34 | }
35 | copyStreamToPg("path_elevations", stream, hikari)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/ExtractWaysFromFilteredRelations.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pbf.Relation
5 | import org.trailcatalog.importers.pipeline.PMapTransformer
6 | import org.trailcatalog.importers.pipeline.collections.Emitter2
7 | import org.trailcatalog.importers.pipeline.collections.PEntry
8 | import org.trailcatalog.models.RelationCategory
9 | import org.trailcatalog.proto.RelationGeometry
10 |
11 | class ExtractWaysFromFilteredRelations(private val category: RelationCategory)
12 | : PMapTransformer, List>>, Long, Long>(
13 | "ExtractWaysFromFilteredRelations",
14 | TypeToken.of(Long::class.java),
15 | TypeToken.of(Long::class.java)) {
16 |
17 | override fun act(
18 | input: PEntry, List>>,
19 | emitter: Emitter2) {
20 | for (value in input.values) {
21 | val isTrail = value.first.any { category.isParentOf(it.type) }
22 | if (isTrail) {
23 | value.second.forEach { emitWays(it, emitter) }
24 | }
25 | }
26 | }
27 |
28 | private fun emitWays(relation: RelationGeometry, emitter: Emitter2) {
29 | for (member in relation.membersList) {
30 | if (member.hasRelation()) {
31 | emitWays(member.relation, emitter)
32 | }
33 | if (member.hasWay()) {
34 | emitter.emit(member.way.wayId, member.way.wayId)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/icons/national-park.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/java/lat/trails/frontend/encrypter.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | const CIPHER_ALGORITHM = 'aes-256-gcm';
4 | const CIPHER_AUTH_TAG_LENGTH = 16;
5 | const CIPHER_KEY_LENGTH = 32;
6 | const CIPHER_IV_LENGTH = 16;
7 | const ENCRYPTED_ENCODING = 'base64';
8 |
9 | export class Encrypter {
10 |
11 | private readonly key: Buffer;
12 |
13 | constructor(secret: string) {
14 | this.key = Buffer.from(secret.slice(0, CIPHER_KEY_LENGTH), 'utf8');
15 | }
16 |
17 | encrypt(s: string): string {
18 | const iv = crypto.randomBytes(CIPHER_IV_LENGTH);
19 | const cipher = crypto.createCipheriv(CIPHER_ALGORITHM, this.key, iv, {
20 | authTagLength: CIPHER_AUTH_TAG_LENGTH,
21 | });
22 | const encryptedVerifier = Buffer.concat([
23 | cipher.update(s, 'utf8'),
24 | cipher.final(),
25 | ]);
26 |
27 | return Buffer.concat([iv, cipher.getAuthTag(), encryptedVerifier]).toString(ENCRYPTED_ENCODING);
28 | }
29 |
30 | decrypt(t: string): string {
31 | const b = Buffer.from(t, ENCRYPTED_ENCODING);
32 | const iv = b.slice(0, CIPHER_IV_LENGTH);
33 | const authTag = b.slice(CIPHER_IV_LENGTH, CIPHER_IV_LENGTH + CIPHER_AUTH_TAG_LENGTH);
34 | const encryptedVerifier = b.slice(CIPHER_IV_LENGTH + CIPHER_AUTH_TAG_LENGTH);
35 | const decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, this.key, iv, {
36 | authTagLength: CIPHER_AUTH_TAG_LENGTH,
37 | });
38 | decipher.setAuthTag(authTag);
39 | return decipher.update(encryptedVerifier, /* inputEncoding= */ undefined, 'utf8') +
40 | decipher.final('utf8');
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/java/lat/trails/frontend/auth.ts:
--------------------------------------------------------------------------------
1 | import { FastifyReply, FastifyRequest } from 'fastify';
2 |
3 | import { Encrypter } from './encrypter';
4 |
5 | const LOGIN_COOKIE = 'logged_in';
6 | const LOGIN_DURATION_MS = 1 * 24 * 60 * 60 * 1000;
7 |
8 | interface SerializedLogin {
9 | created: number;
10 | id: string;
11 | issuerEndpoint: string;
12 | }
13 |
14 | export class ExpiredCredentialError extends Error {
15 |
16 | constructor(readonly issuerEndpoint: string) {
17 | super();
18 | }
19 | }
20 |
21 | export class LoginEnforcer {
22 |
23 | constructor(private readonly encrypter: Encrypter) {
24 | }
25 |
26 | checkLogin(request: FastifyRequest): string|undefined {
27 | const credential = request.cookies[LOGIN_COOKIE];
28 | if (!credential) {
29 | return undefined;
30 | }
31 |
32 | console.log(this.encrypter.decrypt(credential));
33 | const info = JSON.parse(this.encrypter.decrypt(credential));
34 | if (new Date().getTime() < info.created + LOGIN_DURATION_MS) {
35 | return info.id;
36 | } else {
37 | throw new ExpiredCredentialError(info.issuerEndpoint);
38 | }
39 | }
40 |
41 | createFreshLogin(id: string, issuerEndpoint: string, reply: FastifyReply): void {
42 | const created = new Date().getTime();
43 | const login: SerializedLogin = {
44 | created,
45 | id,
46 | issuerEndpoint,
47 | };
48 |
49 | const serialized = this.encrypter.encrypt(JSON.stringify(login));
50 | reply.setCookie(LOGIN_COOKIE, serialized, {
51 | maxAge: LOGIN_DURATION_MS / 1000,
52 | });
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/map/colors.ts:
--------------------------------------------------------------------------------
1 | import { rgbaU32ToHex, rgbaToUint32 } from 'js/map/common/math';
2 | import { RgbaU32 } from 'js/map/common/types';
3 |
4 | export interface LinePalette {
5 | hex: {
6 | fill: string;
7 | stroke: string;
8 | };
9 | raw: {
10 | fill: RgbaU32;
11 | stroke: RgbaU32;
12 | };
13 | }
14 |
15 | export const ACTIVE_PALETTE =
16 | paletteFromFillStroke(rgbaToUint32(1, 0.902, 0, 1), rgbaToUint32(0, 0, 0, 1));
17 |
18 | export const BOUNDARY_PALETTE = {
19 | fill: rgbaToUint32(1, 0.902, 0, 1),
20 | stroke: rgbaToUint32(0, 0, 0, 1),
21 | } as const;
22 | export const BOUNDARY_HEX_PALETTE = {
23 | fill: rgbaU32ToHex(BOUNDARY_PALETTE.fill),
24 | stroke: rgbaU32ToHex(BOUNDARY_PALETTE.stroke),
25 | } as const;
26 |
27 | export const DEFAULT_PALETTE = {
28 | fill: rgbaToUint32(0, 0, 0, 1),
29 | stroke: rgbaToUint32(1, 1, 1, 0),
30 | } as const;
31 | export const DEFAULT_HEX_PALETTE = {
32 | fill: rgbaU32ToHex(DEFAULT_PALETTE.fill),
33 | stroke: rgbaU32ToHex(DEFAULT_PALETTE.stroke),
34 | } as const;
35 |
36 | export const ERROR_PALETTE =
37 | paletteFromFillStroke(rgbaToUint32(0.965, 0.584, 0.584, 1), rgbaToUint32(0, 0, 0, 1));
38 |
39 | export const HOVER_PALETTE =
40 | paletteFromFillStroke(rgbaToUint32(1, 1, 1, 1), rgbaToUint32(0, 0, 0, 1));
41 |
42 | function paletteFromFillStroke(fill: RgbaU32, stroke: RgbaU32): LinePalette {
43 | return {
44 | hex: {
45 | fill: rgbaU32ToHex(fill),
46 | stroke: rgbaU32ToHex(stroke),
47 | },
48 | raw: {
49 | fill,
50 | stroke,
51 | },
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/production/files/trails_lat_frontend.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -ex
4 |
5 | CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/home/april/frontend_key.json \
6 | gcloud auth print-access-token \
7 | | podman login -u oauth2accesstoken --password-stdin us-west1-docker.pkg.dev
8 |
9 | secrets="$(CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=/home/april/frontend_key.json \
10 | gcloud \
11 | secrets \
12 | versions \
13 | access \
14 | latest \
15 | --project trailcatalog \
16 | --secret trails_lat \
17 | --quiet)"
18 |
19 | cookie_secret="$(echo "${secrets}" | jq -r '.cookie_secret')"
20 | google_cid="$(echo "${secrets}" | jq -r '.google_client_id')"
21 | google_secret="$(echo "${secrets}" | jq -r '.google_client_secret')"
22 | pg_user="$(echo "${secrets}" | jq -r '.database_username')"
23 | pg_pwd="$(echo "${secrets}" | jq -r '.database_password')"
24 |
25 | podman run \
26 | --name trails-lat-frontend \
27 | --pull always \
28 | --rm \
29 | --env COOKIE_SECRET="${cookie_secret}" \
30 | --env OAUTH2_GOOGLE_CLIENT_ID="${google_cid}" \
31 | --env OAUTH2_GOOGLE_SECRET="${google_secret}" \
32 | --env PGHOST='127.0.0.1' \
33 | --env PGPORT='5432' \
34 | --env PGDATABASE='trails_lat' \
35 | --env PGPASSWORD="${pg_pwd}" \
36 | --env PGUSER="${pg_user}" \
37 | --network host \
38 | us-west1-docker.pkg.dev/trailcatalog/containers/lat_trails_frontend:latest \
39 | /app/serve.sh \
40 | --database_username_password="${pg_user}:${pg_pwd}" \
41 | --database_url "postgresql://127.0.0.1/trails_lat" # TODO(april): specify schema?
42 |
--------------------------------------------------------------------------------
/trailcatalog/migrations/1_create_readable_ids.toml:
--------------------------------------------------------------------------------
1 | # It's extremely attractive to delete these tables and just use readable_id as the trail id. There
2 | # are several reasons this actually isn't as nice as it seems.
3 | #
4 | # * The map controller and renderers use some hacks that assume it is a bigint ID
5 | # * If it's a text string then it has to get the varsize and then skip an indeterminant number of
6 | # characters.
7 | # * The map data decoder not only has to pull out a variable length string, it has to do utf-8
8 | # decoding to turn the int8[] into a string.
9 | # * It's challenging to compute boundary IDs, so because I am lazy those are likely to stay bigint
10 | # anyway.
11 | # * Ultimately we're shaving O(10) milliseconds off queries by inlining the table. And most of our
12 | # queries are viewport searches that likely slightly benefit from bigint keys.
13 |
14 | [[actions]]
15 | type = "create_table"
16 | name = "trail_identifiers"
17 | primary_key = ["numeric_id", "epoch"]
18 |
19 | [[actions.columns]]
20 | name = "numeric_id"
21 | nullable = false
22 | type = "BIGINT"
23 |
24 | [[actions.columns]]
25 | name = "readable_id"
26 | nullable = false
27 | type = "TEXT"
28 |
29 | [[actions.columns]]
30 | name = "epoch"
31 | nullable = false
32 | type = "INT"
33 |
34 | [actions.partition_by]
35 | list = ["epoch"]
36 |
37 | [[actions]]
38 | type = "add_index"
39 | table = "trail_identifiers"
40 |
41 | [actions.index]
42 | name = "trail_identifiers_readable_idx"
43 | columns = ["readable_id", "epoch"]
44 | concurrently = false
45 | unique = true
46 |
47 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/waterfall.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/ExtractNodes.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf
2 |
3 | import com.google.common.reflect.TypeToken
4 | import crosby.binary.Osmformat.PrimitiveBlock
5 | import org.trailcatalog.importers.pipeline.PTransformer
6 | import org.trailcatalog.importers.pipeline.collections.Emitter
7 |
8 | class ExtractNodes : PTransformer(TypeToken.of(Node::class.java)) {
9 |
10 | override fun act(input: PrimitiveBlock, emitter: Emitter) {
11 | for (group in input.primitivegroupList) {
12 | for (node in group.nodesList) {
13 | val latDegrees = (input.latOffset + input.granularity * node.lat) * NANO
14 | val lngDegrees = (input.lonOffset + input.granularity * node.lon) * NANO
15 | emitter.emit(Node(node.id, LatLngE7(latDegrees.toIntE7(), lngDegrees.toIntE7())))
16 | }
17 |
18 | var denseId = 0L
19 | var denseLat = 0L
20 | var denseLon = 0L
21 | for (index in 0 until group.dense.idCount) {
22 | denseId += group.dense.idList[index]
23 | denseLat += group.dense.latList[index]
24 | denseLon += group.dense.lonList[index]
25 |
26 | val latDegrees = (input.latOffset + input.granularity * denseLat) * NANO
27 | val lngDegrees = (input.lonOffset + input.granularity * denseLon) * NANO
28 | emitter.emit(Node(denseId, LatLngE7(latDegrees.toIntE7(), lngDegrees.toIntE7())))
29 | }
30 | }
31 | }
32 |
33 | override fun estimateRatio(): Double {
34 | return 2.0
35 | }
36 | }
37 |
38 | fun Double.toIntE7(): Int {
39 | return (this * 10_000_000).toInt()
40 | }
41 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/toilets.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/java/lat/trails/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = [":internal"])
2 |
3 | load("@rules_java//java:defs.bzl", "java_binary")
4 | load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
5 | load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
6 | load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
7 |
8 | package_group(
9 | name = "internal",
10 | packages = [
11 | "//java/lat/trails/...",
12 | "//javatests/lat/trails/...",
13 | "//production/...",
14 | ],
15 | )
16 |
17 | pkg_tar(
18 | name = "frontend_pkg",
19 | package_dir = "app",
20 | srcs = [
21 | "//java/lat/trails/frontend:frontend.js",
22 | "//java/lat/trails/frontend:frontend.js.map",
23 | ":api_server_deploy.jar",
24 | ":static_data",
25 | ],
26 | stamp = 1,
27 | )
28 |
29 | pkg_files(
30 | name = "static_data",
31 | srcs = [
32 | "//java/lat/trails/static",
33 | ],
34 | prefix = "static",
35 | strip_prefix = strip_prefix.from_pkg(),
36 | )
37 |
38 | java_binary(
39 | name = "api_server",
40 | main_class = "lat.trails.ApiServerKt",
41 | runtime_deps = [":api_server_lib"],
42 | )
43 |
44 | kt_jvm_library(
45 | name = "api_server_lib",
46 | srcs = glob(["*.kt"]),
47 | deps = [
48 | "//java/lat/trails/common",
49 | "//java/org/trailcatalog/common",
50 | "//java/org/trailcatalog/flags",
51 | "//java/org/trailcatalog/s2:s2-kt",
52 | "@com_google_geometry_s2//:s2",
53 | "@maven//:com_fasterxml_jackson_core_jackson_databind",
54 | "@maven//:io_javalin_javalin",
55 | "@maven//:org_slf4j_slf4j_simple",
56 | ],
57 | )
58 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/routes.ts:
--------------------------------------------------------------------------------
1 | import { exists } from 'external/dev_april_corgi+/js/common/asserts';
2 | import { ViewsService } from 'external/dev_april_corgi+/js/corgi/history/views_service';
3 |
4 | export interface Routes {
5 | boundary_detail: {
6 | id: string;
7 | };
8 | go_to_trail: {
9 | id: string;
10 | };
11 | search_results: {};
12 | trail_detail: {
13 | trail: string;
14 | };
15 | }
16 |
17 | export const routes: {[k in keyof Routes]: RegExp} = {
18 | 'boundary_detail': /^\/boundary\/(?\d+)$/,
19 | 'go_to_trail': /^\/goto\/trail\/(?\d+)$/,
20 | 'search_results': /^\/(search)?$/,
21 | 'trail_detail': /^\/trail\/(?.+)$/,
22 | } as const;
23 |
24 | export function showOverview(
25 | {camera}: {camera?: {lat: number; lng: number; zoom: number;};},
26 | views: ViewsService):
27 | void {
28 | if (camera) {
29 | views.goTo(`/?lat=${camera.lat}&lng=${camera.lng}&zoom=${camera.zoom}`);
30 | } else {
31 | views.goTo('/');
32 | }
33 | }
34 |
35 | export function showSearchResults({boundary, camera, query}: {
36 | boundary?: bigint,
37 | camera?: {lat: number, lng: number, zoom: number},
38 | query?: string,
39 | }, views: ViewsService): void {
40 | const filters = [
41 | boundary ? `boundary=${boundary}` : undefined,
42 | camera ? `lat=${camera.lat}&lng=${camera.lng}&zoom=${camera.zoom}` : undefined,
43 | query ? `query=${encodeURIComponent(query)}` : undefined,
44 | ].filter(exists);
45 | views.goTo(`/search?${filters.join('&')}`);
46 | }
47 |
48 | export function showTrail(id: bigint, views: ViewsService): void {
49 | views.goTo(`/goto/trail/${id}`);
50 | }
51 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/CreateTrailsInBoundaries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.geometry.S2CellUnion
4 | import com.google.common.reflect.TypeToken
5 | import org.trailcatalog.importers.pipeline.PMapTransformer
6 | import org.trailcatalog.importers.pipeline.collections.Emitter2
7 | import org.trailcatalog.importers.pipeline.collections.PEntry
8 | import java.io.ByteArrayInputStream
9 |
10 | class CreateTrailsInBoundaries
11 | : PMapTransformer, List>>, Long, Long>(
12 | "CreateTrailsInBoundaries",
13 | TypeToken.of(Long::class.java),
14 | TypeToken.of(Long::class.java),
15 | ) {
16 |
17 | override fun act(
18 | input: PEntry, List>>,
19 | emitter: Emitter2) {
20 | val polylines = HashMap().also {
21 | for (value in input.values) {
22 | for (trail in value.second) {
23 | val decoded = ByteArrayInputStream(trail.polyline)
24 | it[trail.id] = S2CellUnion.decode(decoded)
25 | }
26 | }
27 | }
28 |
29 | if (polylines.isEmpty()) {
30 | return
31 | }
32 |
33 | for (value in input.values) {
34 | for (boundary in value.first) {
35 | val decoded = ByteArrayInputStream(boundary.polygon)
36 | val polygon = S2CellUnion.decode(decoded)
37 | for (trail in value.second) {
38 | val polyline = polylines[trail.id]!!
39 |
40 | if (polygon.contains(polyline)) {
41 | emitter.emit(trail.id, boundary.id)
42 | }
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/basemap/CreateBoundariesInBoundaries.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.basemap
2 |
3 | import com.google.common.geometry.S2CellUnion
4 | import com.google.common.reflect.TypeToken
5 | import org.trailcatalog.importers.pipeline.PMapTransformer
6 | import org.trailcatalog.importers.pipeline.collections.Emitter2
7 | import org.trailcatalog.importers.pipeline.collections.PEntry
8 | import java.io.ByteArrayInputStream
9 |
10 | class CreateBoundariesInBoundaries
11 | : PMapTransformer, List>>, Long, Long>(
12 | "CreateBoundariesInBoundaries",
13 | TypeToken.of(Long::class.java),
14 | TypeToken.of(Long::class.java),
15 | ) {
16 |
17 | override fun act(
18 | input: PEntry, List>>,
19 | emitter: Emitter2) {
20 | val count = input.values.map { it.first.count() }.sum()
21 | if (count <= 1) {
22 | return
23 | }
24 |
25 | val polygons = HashMap().also {
26 | for (value in input.values) {
27 | for (boundary in value.first) {
28 | val decoded = ByteArrayInputStream(boundary.polygon)
29 | it[boundary.id] = S2CellUnion.decode(decoded)
30 | }
31 | }
32 | }
33 |
34 | for (value in input.values) {
35 | for (child in value.first) {
36 | for (parent in value.first) {
37 | if (child.id == parent.id) {
38 | continue
39 | }
40 |
41 | if (polygons[parent.id]!!.contains(polygons[child.id]!!)) {
42 | emitter.emit(child.id, parent.id)
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/firepit.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/BinaryStructListWriter.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.collections.PCollection
5 | import org.trailcatalog.importers.pipeline.collections.Serializer
6 | import org.trailcatalog.importers.pipeline.collections.getSerializer
7 | import org.trailcatalog.common.ChannelEncodedOutputStream
8 | import org.trailcatalog.common.DelegatingEncodedOutputStream
9 | import org.trailcatalog.common.EncodedOutputStream
10 | import java.io.File
11 | import java.io.FileOutputStream
12 | import java.io.FileWriter
13 | import java.io.RandomAccessFile
14 |
15 | inline fun binaryStructListWriter(file: File): PSink> {
16 | val serializer = getSerializer(TypeToken.of(T::class.java))
17 | return BinaryStructListWriter(file, serializer)
18 | }
19 |
20 | class BinaryStructListWriter(
21 | private val file: File,
22 | private val serializer: Serializer) : PSink>() {
23 |
24 | override fun write(input: PCollection) {
25 | val shards = RandomAccessFile(file, "rw").use {
26 | ChannelEncodedOutputStream(it.channel).use { output ->
27 | while (input.hasNext()) {
28 | serializer.write(input.next(), output)
29 | output.checkBufferSpace()
30 | }
31 | input.close()
32 |
33 | output.shard()
34 | output.shards()
35 | }
36 | }
37 |
38 | DelegatingEncodedOutputStream(FileOutputStream(file.path + ".shards")).use {
39 | for (extent in shards) {
40 | it.writeLong(extent.start)
41 | it.writeLong(extent.length)
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/def.json:
--------------------------------------------------------------------------------
1 | {
2 | "key_type": "PointCategory",
3 | "images": [
4 | {"file": "point.svg"},
5 | {"file": "alpine_hut.svg", "key": "PointCategory.AMENITY_HUT_ALPINE"},
6 | {"file": "barbecue.svg", "key": "PointCategory.AMENITY_FIRE_BARBECUE"},
7 | {"file": "bridge.svg"},
8 | {"file": "camp_site.svg", "key": "PointCategory.AMENITY_CAMP_SITE"},
9 | {"file": "cave_entrance.svg", "key": "PointCategory.NATURAL_CAVE_ENTRANCE"},
10 | {"file": "drinking_water.svg", "key": "PointCategory.AMENITY_WATER_DRINKING"},
11 | {"file": "firepit.svg", "key": "PointCategory.AMENITY_FIRE_PIT"},
12 | {"file": "guidepost.svg", "key": "PointCategory.INFORMATION_GUIDE_POST"},
13 | {"file": "mountain_pass.svg", "key": "PointCategory.WAY_MOUNTAIN_PASS"},
14 | {"file": "parking.svg", "key": "PointCategory.AMENITY_PARKING"},
15 | {"file": "peak.svg", "key": "PointCategory.NATURAL_PEAK"},
16 | {"file": "picnic_table.svg", "key": "PointCategory.AMENITY_PICNIC_TABLE"},
17 | {"file": "saddle.svg", "key": "PointCategory.NATURAL_SADDLE"},
18 | {"file": "shelter.svg", "key": "PointCategory.AMENITY_SHELTER"},
19 | {"file": "toilets.svg", "key": "PointCategory.AMENITY_TOILETS"},
20 | {"file": "trailhead.svg", "key": "PointCategory.WAY_PATH_TRAILHEAD"},
21 | {"file": "viewpoint.svg", "key": "PointCategory.WAY_VIEWPOINT"},
22 | {"file": "visitor_center.svg", "key": "PointCategory.INFORMATION_VISITOR_CENTER"},
23 | {"file": "volcano.svg", "key": "PointCategory.NATURAL_VOLCANO"},
24 | {"file": "waterfall.svg", "key": "PointCategory.NATURAL_WATERFALL"},
25 | {"file": "wilderness_hut.svg", "key": "PointCategory.AMENITY_HUT_WILDERNESS"}
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/js/map/workers/raster_loader.ts:
--------------------------------------------------------------------------------
1 | import { checkExhaustive } from 'external/dev_april_corgi+/js/common/asserts';
2 |
3 | import { TileId } from '../common/types';
4 |
5 | interface InitializeRequest {
6 | kind: 'ir';
7 | }
8 |
9 | interface LoadRequest {
10 | kind: 'lr';
11 | id: TileId;
12 | data: ArrayBuffer;
13 | }
14 |
15 | export type Request = InitializeRequest|LoadRequest;
16 |
17 | export interface LoadResponse {
18 | kind: 'lr';
19 | id: TileId;
20 | bitmap: ImageBitmap;
21 | }
22 |
23 | export type Response = LoadResponse;
24 |
25 | class RasterLoader {
26 |
27 | constructor(
28 | private readonly postMessage: (response: Response, transfer?: Transferable[]) => void,
29 | ) {}
30 |
31 | render(request: LoadRequest) {
32 | createImageBitmap(new Blob([request.data]))
33 | .then(bitmap => {
34 | this.postMessage({
35 | kind: 'lr',
36 | id: request.id,
37 | bitmap,
38 | }, [bitmap]);
39 | }, e => {
40 | console.error(e);
41 | });
42 | }
43 | }
44 |
45 | function start(ir: InitializeRequest) {
46 | const fetcher = new RasterLoader((self as any).postMessage.bind(self));
47 | self.onmessage = e => {
48 | const request = e.data as Request;
49 | if (request.kind === 'ir') {
50 | throw new Error('Already initialized');
51 | } else if (request.kind === 'lr') {
52 | fetcher.render(request);
53 | } else {
54 | checkExhaustive(request);
55 | }
56 | };
57 | }
58 |
59 | self.onmessage = e => {
60 | const request = e.data as Request;
61 | if (request.kind !== 'ir') {
62 | throw new Error('Expected an initialization request');
63 | }
64 |
65 | start(request);
66 | };
67 |
68 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/volcano.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/route_controller.ts:
--------------------------------------------------------------------------------
1 | import { checkExists } from 'external/dev_april_corgi+/js/common/asserts';
2 | import { Controller, Response } from 'external/dev_april_corgi+/js/corgi/controller';
3 | import { DiscriminatedRoute, matchPath, ViewsService } from 'external/dev_april_corgi+/js/corgi/history/views_service';
4 | import { currentUrl } from 'external/dev_april_corgi+/js/server/ssr_aware';
5 |
6 | import { Routes, routes } from './routes';
7 |
8 | export interface State {
9 | active: DiscriminatedRoute;
10 | parameters: {[key: string]: string};
11 | }
12 |
13 | type Deps = typeof RouteController.deps;
14 |
15 | export class RouteController extends Controller<{}, Deps, HTMLDivElement, State> {
16 |
17 | static getInitialState(): State {
18 | const url = currentUrl();
19 | return {
20 | active: checkExists(matchPath(url.pathname, routes)),
21 | parameters: Object.fromEntries(new URLSearchParams(url.search).entries()),
22 | };
23 | }
24 |
25 | static deps() {
26 | return {
27 | services: {
28 | views: ViewsService,
29 | },
30 | };
31 | }
32 |
33 | private readonly views: ViewsService;
34 |
35 | constructor(response: Response) {
36 | super(response);
37 | this.views = response.deps.services.views;
38 | this.views.addListener(this);
39 | this.views.addRoutes(routes);
40 |
41 | this.registerDisposer(() => {
42 | this.views.removeListener(this);
43 | });
44 | }
45 |
46 | routeChanged(active: DiscriminatedRoute, parameters: {[key: string]: string}):
47 | Promise {
48 | return this.updateState({
49 | active,
50 | parameters,
51 | });
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/client/common/math.ts:
--------------------------------------------------------------------------------
1 | import { Long } from 'java/org/trailcatalog/s2';
2 | import { LatLng, LatLngRect, LatLngZoom, RgbaU32, Vec2, Vec4 } from 'js/map/common/types';
3 |
4 | export function celsiusToFahrenheit(celsius: number): number {
5 | return 1.8 * celsius + 32;
6 | }
7 |
8 | export function metersToFeet(meters: number): number {
9 | return meters * 3.28084;
10 | }
11 |
12 | export function metersToMiles(meters: number): number {
13 | return meters * 0.00062137119224;
14 | }
15 |
16 | export function degreesE7ToLatLng(lat: number, lng: number): LatLng {
17 | return [
18 | lat / 10_000_000,
19 | lng / 10_000_000,
20 | ] as const as LatLng;
21 | }
22 |
23 | export function projectLatLng(latLng: LatLng): Vec2 {
24 | const radians = [latLng[0] / 180 * Math.PI, latLng[1] / 180 * Math.PI];
25 | const x = radians[1] / Math.PI;
26 | const y = Math.log((1 + Math.sin(radians[0])) / (1 - Math.sin(radians[0]))) / (2 * Math.PI);
27 | return [x, y];
28 | }
29 |
30 | const reinterpretLongBuffer = new ArrayBuffer(8);
31 | const reinterpretFloatArray = new Float64Array(reinterpretLongBuffer);
32 | const reinterpretIntArray = new Int32Array(reinterpretLongBuffer);
33 |
34 | /** Reads a float using the bits of a bigint. */
35 | export function reinterpretBigInt(v: bigint): number {
36 | const mask = 0xFFFFFFFFn;
37 | reinterpretIntArray[0] = Number((v >> 32n) & mask);
38 | reinterpretIntArray[1] = Number(v & mask);
39 | return reinterpretFloatArray[0];
40 | }
41 |
42 | /** Reads a float using the bits of a Closure Long. */
43 | export function reinterpretLong(v: Long): number {
44 | reinterpretIntArray[0] = v.getHighBits();
45 | reinterpretIntArray[1] = v.getLowBits();
46 | return reinterpretFloatArray[0];
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/javatests/org/trailcatalog/importers/pipeline/io/EncodedStreamsTest.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline.io
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 | import org.trailcatalog.common.ChannelEncodedOutputStream
6 | import org.trailcatalog.common.EncodedByteBufferInputStream
7 | import java.io.File
8 | import java.io.RandomAccessFile
9 | import java.nio.channels.FileChannel.MapMode
10 |
11 | class EncodedStreamsTest {
12 |
13 | @Test
14 | fun testStuff() {
15 | val file = File.createTempFile("test", "")
16 | file.deleteOnExit()
17 |
18 | val length = RandomAccessFile(file, "rw").use {
19 | it.channel.use { channel ->
20 | ChannelEncodedOutputStream(channel).use { output ->
21 | output.write(3)
22 | output.writeInt(30)
23 | output.writeInt(3_000)
24 | output.writeLong(300_000_000_000)
25 | output.writeVarInt(-3)
26 | output.writeVarInt(3)
27 | output.writeVarInt(30_000)
28 | output.writeVarInt(30_000_000)
29 | output.flush()
30 | output.position()
31 | }
32 | }
33 | }
34 |
35 | RandomAccessFile(file, "r").use {
36 | val input = EncodedByteBufferInputStream(it.channel.map(MapMode.READ_ONLY, 0, length))
37 | assertThat(input.read()).isEqualTo(3)
38 | assertThat(input.readInt()).isEqualTo(30)
39 | assertThat(input.readInt()).isEqualTo(3_000)
40 | assertThat(input.readLong()).isEqualTo(300_000_000_000)
41 | assertThat(input.readVarInt()).isEqualTo(-3)
42 | assertThat(input.readVarInt()).isEqualTo(3)
43 | assertThat(input.readVarInt()).isEqualTo(30_000)
44 | assertThat(input.readVarInt()).isEqualTo(30_000_000)
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/java/lat/trails/static/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//java/lat/trails:internal"])
2 |
3 | load("@aspect_rules_js//js:defs.bzl", "js_library")
4 |
5 | filegroup(
6 | name = "static",
7 | srcs = [
8 | ":copies",
9 | ":glob",
10 | ],
11 | )
12 |
13 | js_library(
14 | name = "glob",
15 | srcs = glob(["*.webp"], allow_empty=True),
16 | )
17 |
18 | genrule(
19 | name = "copies",
20 | srcs = [
21 | "//java/lat/trails/client",
22 | "//java/lat/trails/client/workers:collection_loader_worker",
23 | "//js/map/workers:earth_search_loader_worker",
24 | "//js/map/workers:location_querier_worker",
25 | "//js/map/workers:mbtile_loader_worker",
26 | "//js/map/workers:raster_loader_worker",
27 | "//js/map/workers:s2_data_fetcher_worker",
28 | "//js/map/workers:xyz_data_fetcher_worker",
29 | ],
30 | outs = [
31 | "client.css",
32 | "client.js",
33 | "client.js.map",
34 | "collection_loader_worker.js",
35 | "collection_loader_worker.js.map",
36 | "earth_search_loader_worker.js",
37 | "earth_search_loader_worker.js.map",
38 | "location_querier_worker.js",
39 | "location_querier_worker.js.map",
40 | "mbtile_loader_worker.js",
41 | "mbtile_loader_worker.js.map",
42 | "raster_loader_worker.js",
43 | "raster_loader_worker.js.map",
44 | "s2_data_fetcher_worker.js",
45 | "s2_data_fetcher_worker.js.map",
46 | "xyz_data_fetcher_worker.js",
47 | "xyz_data_fetcher_worker.js.map",
48 | ],
49 | cmd = "\n".join([
50 | "for i in $(SRCS); do",
51 | " cp \"$${i}\" \"$(@D)/$$(basename \"$${i}\")\"",
52 | "done",
53 | ]),
54 | )
55 |
56 |
--------------------------------------------------------------------------------
/js/map/workers/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("//build_defs:ts.bzl", "esbuild_binary", "tc_ts_project")
4 |
5 | # TODO(april): geotiff.js has bad dependencies on node modules that break easbuild. So we disable
6 | # sandboxing which somehow helps. Maybe the bazel sandbox plugin tells esbuild the node modules are
7 | # paths?
8 | esbuild_binary(
9 | name = "earth_search_loader_worker",
10 | entry_point = "earth_search_loader.ts",
11 | deps = [
12 | ":workers",
13 | ],
14 | )
15 |
16 | esbuild_binary(
17 | name = "location_querier_worker",
18 | entry_point = "location_querier.ts",
19 | deps = [
20 | ":workers",
21 | ],
22 | )
23 |
24 | esbuild_binary(
25 | name = "mbtile_loader_worker",
26 | entry_point = "mbtile_loader.ts",
27 | deps = [
28 | ":workers",
29 | ],
30 | )
31 |
32 | esbuild_binary(
33 | name = "raster_loader_worker",
34 | entry_point = "raster_loader.ts",
35 | deps = [
36 | ":workers",
37 | ],
38 | )
39 |
40 | esbuild_binary(
41 | name = "s2_data_fetcher_worker",
42 | entry_point = "s2_data_fetcher.ts",
43 | deps = [
44 | ":workers",
45 | ],
46 | )
47 |
48 | esbuild_binary(
49 | name = "xyz_data_fetcher_worker",
50 | entry_point = "xyz_data_fetcher.ts",
51 | deps = [
52 | ":workers",
53 | ],
54 | )
55 |
56 | tc_ts_project(
57 | name = "workers",
58 | deps = [
59 | "//:node_modules/@types/earcut",
60 | "//:node_modules/earcut",
61 | "//:node_modules/geotiff",
62 | "//java/org/trailcatalog/s2",
63 | "//js/map",
64 | "//js/map/common",
65 | "//js/map/rendering",
66 | "@dev_april_corgi//js/common",
67 | ],
68 | )
69 |
70 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pbf/ExtractWayRelationPairs.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pbf
2 |
3 | import com.google.common.reflect.TypeToken
4 | import org.trailcatalog.importers.pipeline.PMapTransformer
5 | import org.trailcatalog.importers.pipeline.collections.Emitter2
6 | import org.trailcatalog.importers.pipeline.collections.PEntry
7 | import org.trailcatalog.proto.RelationGeometry
8 | import org.trailcatalog.proto.RelationMember.ValueCase.NODE_ID
9 | import org.trailcatalog.proto.RelationMember.ValueCase.RELATION
10 | import org.trailcatalog.proto.RelationMember.ValueCase.WAY
11 |
12 | class ExtractWayRelationPairs : PMapTransformer, Long, Long>(
13 | "ExtractWayRelationPairs",
14 | TypeToken.of(Long::class.java),
15 | TypeToken.of(Long::class.java),
16 | ) {
17 |
18 | override fun act(input: PEntry, emitter: Emitter2) {
19 | if (input.values.isEmpty()) {
20 | return
21 | }
22 |
23 | val geometry = input.values[0]
24 | val seen = HashSet()
25 | inflate(input.key, geometry, seen, emitter)
26 | }
27 |
28 | override fun estimateRatio(): Double {
29 | return 1.0
30 | }
31 | }
32 |
33 | private fun inflate(
34 | root: Long,
35 | geometry: RelationGeometry,
36 | seen: MutableSet,
37 | emitter: Emitter2,
38 | ) {
39 | for (member in geometry.membersList) {
40 | when (member.valueCase) {
41 | NODE_ID -> {}
42 | RELATION -> inflate(root, member.relation, seen, emitter)
43 | WAY -> {
44 | val wayId = member.way.wayId
45 | if (!seen.contains(wayId)) {
46 | emitter.emit(member.way.wayId, root)
47 | seen.add(wayId)
48 | }
49 | }
50 | else -> throw AssertionError()
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/s2/index.ts:
--------------------------------------------------------------------------------
1 | import * as raw from './s2_raw';
2 |
3 | export const ArrayList = raw.java.util.ArrayList;
4 | export type ArrayList = java.util.ArrayList;
5 | export const Long = raw.nativebootstrap.Long;
6 | export type Long = nativebootstrap.Long;
7 | export const S1Angle = raw.com.google.common.geometry.S1Angle;
8 | export type S1Angle = com.google.common.geometry.S1Angle;
9 | export const S2Cell = raw.com.google.common.geometry.S2Cell;
10 | export type S2Cell = com.google.common.geometry.S2Cell;
11 | export const S2CellId = raw.com.google.common.geometry.S2CellId;
12 | export type S2CellId = com.google.common.geometry.S2CellId;
13 | export const S2CellUnion = raw.com.google.common.geometry.S2CellUnion;
14 | export type S2CellUnion = com.google.common.geometry.S2CellUnion;
15 | export const S2LatLng = raw.com.google.common.geometry.S2LatLng;
16 | export type S2LatLng = com.google.common.geometry.S2LatLng;
17 | export const S2LatLngRect = raw.com.google.common.geometry.S2LatLngRect;
18 | export type S2LatLngRect = com.google.common.geometry.S2LatLngRect;
19 | export const S2Loop = raw.com.google.common.geometry.S2Loop;
20 | export type S2Loop = com.google.common.geometry.S2Loop;
21 | export const S2Point = raw.com.google.common.geometry.S2Point;
22 | export type S2Point = com.google.common.geometry.S2Point;
23 | export const S2Polygon = raw.com.google.common.geometry.S2Polygon;
24 | export type S2Polygon = com.google.common.geometry.S2Polygon;
25 | //export const S2PolygonBuilder = raw.com.google.common.geometry.S2PolygonBuilder;
26 | //export type S2PolygonBuilder = com.google.common.geometry.S2PolygonBuilder;
27 | //export const S2PolygonBuilderOptions = raw.com.google.common.geometry.S2PolygonBuilderOptions;
28 | //export type S2PolygonBuilderOptions = com.google.common.geometry.S2PolygonBuilderOptions;
29 |
30 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/trailhead.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/js/dino/fabric.tsx:
--------------------------------------------------------------------------------
1 | import * as corgi from 'external/dev_april_corgi+/js/corgi';
2 |
3 | import './fabric-icons-inline.css';
4 |
5 | // Fabric's official definitions are missing icons, so we just do it ourselves.
6 | const FABRIC_ICON_CODES = [
7 | 'BlowingSnow',
8 | 'CharticulatorLine',
9 | 'Checkbox',
10 | 'CheckboxCompositeReversed',
11 | 'ChevronDownMed',
12 | 'ChevronUpMed',
13 | 'ChromeBack',
14 | 'ChromeClose',
15 | 'Cloudy',
16 | 'CollapseMenu',
17 | 'Fog',
18 | 'Feedback',
19 | 'Globe',
20 | 'LightSnow',
21 | 'Location',
22 | 'Market',
23 | 'MarketDown',
24 | 'MostySunnyDay',
25 | 'Nav2DMapView',
26 | 'PartlySunnyDay',
27 | 'PartlySunnyFlurriesDay',
28 | 'PartlySunnyShowersDay',
29 | 'PartlySunnyTStormsDay',
30 | 'Rain',
31 | 'RainSnow',
32 | 'ScaleVolume',
33 | 'Search',
34 | 'Snow',
35 | 'SortDown',
36 | 'SortUp',
37 | 'Sunny',
38 | 'Thunderstorms',
39 | 'ZoomToFit',
40 | ] as const;
41 |
42 | /**
43 | * A copyable list for use in the https://uifabricicons.azurewebsites.net/ tool:
44 | EA02
45 | E639
46 | E739
47 | E73D
48 | E972
49 | E971
50 | E830
51 | E8BB
52 | E9BF
53 | EF66
54 | E9CB
55 | ED15
56 | E774
57 | EA02
58 | E81D
59 | EAFC
60 | EF42
61 | E468
62 | E800
63 | E469
64 | E472
65 | E46E
66 | E470
67 | E9C4
68 | E9C7
69 | F18C
70 | E721
71 | E9C8
72 | EE69
73 | EE68
74 | E9BD
75 | E9C6
76 | F649
77 | */
78 |
79 | export type FabricIconName = (typeof FABRIC_ICON_CODES)[keyof typeof FABRIC_ICON_CODES];
80 |
81 | export function FabricIcon({
82 | name,
83 | className,
84 | ...props
85 | }: {
86 | name: FabricIconName,
87 | className?: string,
88 | } & corgi.Properties) {
89 | return <>
90 |
94 | >;
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/trailcatalog/migrations/1_create_elevation.toml:
--------------------------------------------------------------------------------
1 | [[actions]]
2 | type = "create_table"
3 | name = "digital_elevation_models"
4 | primary_key = ["namespace", "id"]
5 |
6 | [[actions.columns]]
7 | name = "namespace"
8 | nullable = false
9 | type = "TEXT"
10 |
11 | [[actions.columns]]
12 | name = "id"
13 | nullable = false
14 | type = "TEXT"
15 |
16 | [[actions.columns]]
17 | name = "date"
18 | nullable = false
19 | type = "TIMESTAMPTZ"
20 |
21 | [[actions.columns]]
22 | name = "resolution_radians"
23 | nullable = false
24 | type = "DOUBLE PRECISION"
25 |
26 | [[actions.columns]]
27 | name = "lat_bound_degrees"
28 | nullable = false
29 | type = "INT4RANGE"
30 |
31 | [[actions.columns]]
32 | name = "lng_bound_degrees"
33 | nullable = false
34 | type = "INT4RANGE"
35 |
36 | [[actions.columns]]
37 | name = "url"
38 | nullable = false
39 | type = "TEXT"
40 |
41 | [[actions]]
42 | type = "add_index"
43 | table = "digital_elevation_models"
44 |
45 | [actions.index]
46 | name = "digital_elevation_models_by_lat_lng_idx"
47 | columns = ["lat_bound_degrees", "lng_bound_degrees"]
48 | type = "gist"
49 |
50 | [[actions]]
51 | type = "create_table"
52 | name = "path_elevations"
53 | primary_key = ["id", "epoch"]
54 |
55 | [[actions.columns]]
56 | name = "id"
57 | nullable = false
58 | type = "BIGINT"
59 |
60 | [[actions.columns]]
61 | name = "epoch"
62 | nullable = false
63 | type = "INT"
64 |
65 | [[actions.columns]]
66 | name = "down_meters"
67 | nullable = false
68 | type = "REAL"
69 |
70 | [[actions.columns]]
71 | name = "up_meters"
72 | nullable = false
73 | type = "REAL"
74 |
75 | [[actions.columns]]
76 | name = "height_samples_10m_meters"
77 | nullable = false
78 | type = "BYTEA"
79 |
80 | [actions.partition_by]
81 | list = ["epoch"]
82 |
83 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/static/images/atlases/points/visitor_center.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/importers/pipeline/BinaryStructListReader.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.importers.pipeline
2 |
3 | import com.google.common.collect.ImmutableList
4 | import com.google.common.reflect.TypeToken
5 | import org.trailcatalog.importers.pipeline.collections.Serializer
6 | import org.trailcatalog.importers.pipeline.collections.getSerializer
7 | import org.trailcatalog.common.EncodedByteBufferInputStream
8 | import org.trailcatalog.common.EncodedInputStream
9 | import org.trailcatalog.common.Extents
10 | import java.io.File
11 | import java.io.RandomAccessFile
12 | import java.nio.channels.FileChannel.MapMode
13 |
14 | inline fun binaryStructListReader(file: File): PSource {
15 | val serializer = getSerializer(TypeToken.of(T::class.java))
16 | return BinaryStructListReader(file, serializer)
17 | }
18 |
19 | class BinaryStructListReader(
20 | private val file: File,
21 | private val serializer: Serializer) : PSource() {
22 |
23 | override fun read() = sequence {
24 | val shardsPath = File(file.path + ".shards")
25 | val shards = RandomAccessFile(shardsPath, "r").use {
26 | val map = it.channel.map(MapMode.READ_ONLY, 0, shardsPath.length())
27 | val shards = ImmutableList.builder()
28 | EncodedByteBufferInputStream(map).use { input ->
29 | while (input.hasRemaining()) {
30 | shards.add(Extents(input.readLong(), input.readLong()))
31 | }
32 | }
33 | shards.build()
34 | }
35 |
36 | for (shard in shards) {
37 | RandomAccessFile(file, "r").use {
38 | val map = it.channel.map(MapMode.READ_ONLY, shard.start, shard.length)
39 | EncodedByteBufferInputStream(map).use { input ->
40 | while (input.hasRemaining()) {
41 | yield(serializer.read(input))
42 | }
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/Tiff.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.common
2 |
3 | enum class TiffTagType(val id: UShort) {
4 | // https://www.awaresystems.be/imaging/tiff/tifftags/baseline.html
5 | // https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
6 | ImageWidth(256.toUShort()),
7 | ImageHeight(257.toUShort()),
8 | BitsPerSample(258.toUShort()),
9 | Compression(259.toUShort()),
10 | PhotometricInterpretation(262.toUShort()),
11 | SamplesPerPixel(277.toUShort()),
12 | PlanarConfiguration(284.toUShort()),
13 | Predictor(317.toUShort()),
14 | TileWidth(322.toUShort()),
15 | TileLength(323.toUShort()), // aka height
16 | TileOffsets(324.toUShort()),
17 | TileByteCounts(325.toUShort()),
18 | SubIFDs(330.toUShort()),
19 | SampleFormat(339.toUShort()),
20 | ModelPixelScaleTag(33550.toUShort()),
21 | ModelTiepointTag(33922.toUShort()),
22 | GeoKeyDirectoryTag(34735.toUShort()),
23 | GeoDoubleParamsTag(34736.toUShort()),
24 | GeoAsciiParamsTag(34737.toUShort()),
25 | GDAL_METADATA(42112.toUShort()),
26 | GDAL_NODATA(42113.toUShort()),
27 | }
28 |
29 | enum class TiffDataType(val id: kotlin.UShort) {
30 | // http://paulbourke.net/dataformats/tiff/tiff_summary.pdf
31 | UByte(1.toUShort()),
32 | Ascii(2.toUShort()),
33 | UShort(3.toUShort()),
34 | UInt(4.toUShort()),
35 | Float(11.toUShort()),
36 | Double(12.toUShort()),
37 | }
38 |
39 | enum class CompressionType(val id: UShort) {
40 | // https://www.awaresystems.be/imaging/tiff/tifftags/compression.html
41 | None(1.toUShort()),
42 | Lzw(5.toUShort()),
43 | AdobeDeflate(8.toUShort()),
44 | }
45 |
46 | enum class PredictorType(val id: UShort) {
47 | None(1.toUShort()),
48 | Horizontal(2.toUShort()),
49 | FloatingPoint(3.toUShort()),
50 | }
51 |
52 | enum class SampleFormat(val id: UShort) {
53 | UInt(1.toUShort()),
54 | Int(2.toUShort()),
55 | Float(3.toUShort()),
56 | }
57 |
--------------------------------------------------------------------------------
/java/org/trailcatalog/common/EncodedByteBufferInputStream.kt:
--------------------------------------------------------------------------------
1 | package org.trailcatalog.common
2 |
3 | import java.lang.reflect.Method
4 | import java.nio.ByteBuffer
5 | import java.nio.MappedByteBuffer
6 | import kotlin.math.min
7 |
8 | class EncodedByteBufferInputStream(private val buffer: ByteBuffer) : EncodedInputStream() {
9 |
10 | override fun available(): Int {
11 | return buffer.limit() - buffer.position()
12 | }
13 |
14 | fun hasRemaining(): Boolean {
15 | return buffer.hasRemaining()
16 | }
17 |
18 | override fun position(): Int {
19 | return buffer.position()
20 | }
21 |
22 | override fun seek(position: UInt) {
23 | if (position > Int.MAX_VALUE.toUInt()) {
24 | throw IllegalArgumentException("Unable to seek past Int.MAX_VALUE")
25 | }
26 | buffer.position(position.toInt())
27 | }
28 |
29 | override fun size(): Int {
30 | return buffer.limit()
31 | }
32 |
33 | override fun readUnsafe(): Byte {
34 | return buffer.get()
35 | }
36 |
37 | override fun read(b: ByteArray, off: Int, len: Int): Int {
38 | val count = min(len, buffer.limit() - buffer.position())
39 | buffer.get(b, off, count)
40 | return count
41 | }
42 |
43 | override fun close() {
44 | if (buffer is MappedByteBuffer) {
45 | close(buffer)
46 | }
47 | }
48 |
49 | companion object {
50 |
51 | private val unsafe: Any
52 | private val invokeCleaner: Method
53 |
54 | init {
55 | val unsafeClazz = Class.forName("sun.misc.Unsafe")
56 | unsafe = unsafeClazz.getDeclaredField("theUnsafe").apply {
57 | isAccessible = true
58 | }.get(null)
59 | invokeCleaner = unsafeClazz.getMethod("invokeCleaner", ByteBuffer::class.java).apply {
60 | isAccessible = true
61 | }
62 | }
63 |
64 | private fun close(buffer: MappedByteBuffer) {
65 | invokeCleaner.invoke(unsafe, buffer)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------