├── 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 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/org/trailcatalog/static/images/atlases/points/point.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/org/trailcatalog/static/images/atlases/points/camp_site.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/org/trailcatalog/static/images/atlases/points/mountain_pass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 6 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /java/org/trailcatalog/static/images/logomark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 |