create panoramas to show what is across the ocean
Python JavaScript HTML Shell
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
data
munsell
static
templates
.gitignore
README.md
config.py
geodesy.py
landfall.py
photosphere.py
process_data.py
requirements.txt
server.py
shp_to_csv.sh
util.py

README.md

Preparation

You must first build the coastline index. Run process_data.py to kick off the process. This will:

  • pull the latest coastline data from openstreetmap servers.
  • amend the coastline data with some hard-coded corrections.
  • build a spatial index of the coastline.
  • tag coastline with administrative areas.
  • perform validation checks.

The whole process may take up to several hours and consume several gigabytes of disk.

Outputs are stored in data/tmp/. Only the tagged_coastline file need be kept once the process completes, but if the pipeline is re-run it will re-use any interim outputs still present in the directory. The pipeline may also be re-run in the following modes:

  • --refresh_coast -- pulls new coast data from openstreetmap
  • --update_coast_corrections -- changes to coast corrections (in data/no_land/)
  • --update_admin_areas -- changes to admin areas (in data/admin_areas or data/admin_index.csv)

These flags may be combined.

Validation

Several sanity checks are performed in the final stage of building the coastline index. You may see the following errors:

  • "unexpected unclaimed land" -- this means there is land that is not claimed by any entity. This could mean several things:

    • Most rarely, the land is actually unclaimed -- real terra nullius -- in which case it should be noted in config.expected_unclaimed to suppress the warning.

    • The land is actually part of a country and the country border should be edited to include it. This scenario can take several forms:

      • The maritime border is shoddily drawn and misses an outlying island.

      • The land is disputed and omitted from either country's territory in an effort to be 'diplomatic'. In this case both country's borders should be drawn to include the land and then handle the conflict under "disputed areas" below.

      • Adjacent country borders don't exactly line up and leave a small sliver of unclaimed land between them.

    • Most probably, the land is a phantom island that doesn't actually exist.

  • "parent without a subdivision" -- this means there is land that is part of a parent entity that has subdivisions, but the land is not part of any subdivision.

    • Some countries are not fully decomposed into subdivisions. For such countries, specify 'all' in config.incompletely_subdivided to suppress the warning.

    • Almost all the scenarios for "unclaimed land" still apply:

      • Land is 'unclaimed' by any subdivision within the country, in which case the area should be specified in config.incompletely_subdivided to suppress the warning.

      • The subdivision border is drawn such that it misses an outlying island.

      • Borders between adjacent subdivisions may leave slivers.

      • The land isn't actually real.

  • "subdivision not covered by parent" -- this means the land is part of a country subdivision but is not included in the territory of the parent country. This is always an error-- the parent country boundary must be a superset of its subdivisions. Either the parent or subdivision boundary must be modified to make this invariant hold.

  • "unexpected disputed area" -- this means the land is claimed by more than one entity.

    • If this is a genuine conflict, mark in config.disputed_areas or config.defacto to suppress the warning and ensure proper rendering (the disputed area can be assigned to either entity during rendering).

    • Adjacent borders may slightly overlap to create slivers of disputed area. The borders should be made to match up.

    • This also occurs when a border exactly follows coastline (which is a common occurrence in real life). This is one case where the checker could be smarter to figure out which entity actually claims the 'land' side of the coast. However, it is left as a warning because boundaries exactly following the coastline is a bad idea. The coastline may be updated dynamically while the boundaries database is static, so if at some future point the coastline is made more precise, the boundary will no longer coincide and you'll get artifacts. Set the boundary buffered some distance from the coast to fix this warning.

Usage

Launch the server: python server.py

Generate a panorama

Visit http://localhost:8000/landfall?origin=<lat>,<lon>&size=<size>, where you fill in <lat>, <lon>, and <size>. <size> is the number of raw samples to take and should be 4-8x the desired width of your final panorama.

Other supported parameters:

  • range=<start>,<end> -- only compute between the compass bearings <start> and <end> (the default is full 360°).
  • mindist=<n> -- ignore any land within <n> meters of the origin point.

This will load the coastline index and compute the closest landfall in all directions. Loading the index the first time takes several minutes. Afterward the index stays loaded in memory and subsequent runs are much faster.

The generated data is saved in output/ and browseable from the server main page http://localhost:8000/. Once complete, the data will also be rendered into a panorama with default parameters.

Render a panorama

There are many settings to change the appearance of the rendered panorama. Add them as url params to the http://localhost:8000/render/... url to use them.

Sizing and labeling

  • width=<pixels> -- width of rendered image
  • height=<pixels> -- height of rendered image
  • distunit=<unit> -- unit to indicate distance: km, mi, nmi (nautical miles), deg (declination -- not degrees of arc), or none (no y-axis labels)
  • yticks=<n>,<n>,<n> -- tick marks for the distance (y) scale. Comma-separated list of numbers in the current axis unit. The string antip may also be used in lieu of a number to designate the distance to antipode.
  • ylabelrepeat=<n> -- repeat the y-axis labels <n> times across the width of the image
  • attrib1=<text> -- attribution text for the lower-right corner. Defaults to OpenStreetMap attribution. Set to empty text to remove. OpenStreetMap attribution is required in some form for publicly released images.
  • attrib2=<text> -- attribution text for the upper-right corner. Defaults to attribution for this project. Set to empty text to remove. Public attribution is appreciated.
  • min_downscale=<n> -- the generated panorama data is downscaled for rendering to give a smoother appearance. However, if too much downscaling is used, tiny islands may become very faint and even lost. If more than 2^(n+1) factor of downscaling is required, the source data is resampled first. Basically, the minimum opacity of the tiniest island will be 100% / 2^(n+1) (12.5% for the default value of 2).

Cropping and layout

  • mindist=<meters> -- near limit of rendered distance
  • maxdist=<meters> -- far limit of rendered distance
  • left=<a> -- set the compass bearing of the left edge of the image
  • right=<a> -- set the compass bearing of the right edge of the image
  • trimleft=<n> -- trim <n> degrees of the left edge of the image
  • trimright=<n> -- trim <n> degrees of the right edge of the image
  • bearcenter=<a> -- center the image on this compass bearing
  • bearmargin=<n> -- add <n> degrees of wraparound to the edges of a 360° panorama (negative values allowed)

Admin areas

  • forcecolor=<admin>:<n>,<admin>:<n> -- force admin area with code <admin> to have a given color number <n> (0-indexed)
  • diffcolor=<admin>:<admin>,<admin>:<admin>:<admin> -- force all groups of admin areas separated by colons to have different colors
  • nosubdiv=<admin>,<admin> -- do not render subdivisions of the specified admin areas
  • resolve=<admin>:<admin>,<admin>:<admin> -- for each pair of admin areas A:B, render A as if it were part of B

Color palette

  • numcolors=<n> -- number of distinct colors to use for admin areas.
  • hues=<hue>,<hue>,<hue> -- actual hues (0--360) to use for coloring admin areas. Overrides numcolors.
  • lumclose=<f> -- luminance (0--1) of the nearest rendered distance
  • lumfar=<f> -- luminance (0--1) of the farthest rendered distance
  • satclose=<f> -- saturation (0 -- ~1) of the nearest rendered distance
  • satfar=<f> -- saturation (0 -- ~1) of the farthest rendered distance
  • colorneardist=<meters> -- use a different reference 'closest distance' for color rendering
  • colorfardist=<meters> -- use a different reference 'farthest distance' for color rendering
  • randseed=<n> -- random seed. Specify to get deterministic results across renders. The seed used in a given render is saved in the export params.

Annotate a panorama

Once you're satisfied with the appearance of the panorama, click 'export png' to save the image to disk and also write out the parameters used to render it.

It is likely you'll want to add labels in an external image editor. 'launch map' is useful to figure out which landforms are which.

Alternate output formats

A landfall panorama may also be exported as KML.

Use the photosphere.py script to turn a panorama image (even one that has been edited with annotations) into a photosphere. The .params file is needed. Do not crop the rendered image.

Limitations for "threading the needle" applications

Landfall's primary goal is to compute 'first-person' views -- that is, all directions from a single coastal point, at a resolution similar to human perception.

It is fun to compute trivia like 'is land X visible from Y', where X and Y are unintuitively far apart and the line of sight between them has to 'thread the needle' between intervening landmasses. Landfall can be used to compute and verify such claims, but this is ancillary to its main purpose, so there are several design compromises that make your work cut out for you.

Landfall samples at fixed angular resolution, and tracks the closest land for each bearing. This is so the size of the result is bound to the size of your output panorama, not to the complexity of the arbitrarily detailed far-off coastline. Narrow passages to farther land will be obscured if they subtend an angle smaller than the min resolution. Resolution can be increased as high as you want, but you must decide how much is enough, and your field of view will have to be shrunk accordingly.

Needle-threading is also extremely sensitive to starting position. When determining if there is line of sight from X to Y, you don't really care exactly where on X and Y you start/end. Landfall is incapable of performing this kind of exhaustive search over a region of coastline (and seems to me like it would be a very difficult algorithm to conceptualize in general). So you must hunt manually, trying different vantage points, never sure if you've fully ruled out a possibility.

Landfall also uses a spherical model of the Earth for simplicity, and because the intended use case typically doesn't require enough precision where it makes a difference. When dealing with very tight tolerances, accounting for the Earth's true spheroid shape may completely change the result. The basic underlying algorithm is compatible, however, with a more complex geodesic calculation, were it to be implemented, though it would slow things down quite a bit.

Then there are considerations of just how precise the coastline source data is, or really ever can be. Beyond a certain scale it's meaningless to pinpoint coastline to a single precise line, due to tides, crags, reefs, etc. Even if you do successfully find a vantage point that threads the needle, it's still hard to grasp how fragile it is in light of the coastline changing even a little bit.

tl;dr -- while Landfall is probably one of the best tools around for answering these trivia questions, there is still a lot of manual work you have to do yourself if you want to stay intellectually honest about the result.