Gridded Projections
The goals of this project were to give web maps the freedom to select an appropriate presentation for their project and to provide access to the wide number of projections that are currently available. The specific goals were to provide:
- A wide range of projections from various projection families
- Support the full range of projection settings that would be of interest to web developers
- Allow projection on the fly within the capabilities of the browsers
- Allow for forward and backward projection (so projected coordinates on a map can be converted to geographic coordinates)
Analysis
Available Projection Engines
We have evaluated the available JavaScript engines and found them to have limited projection support and crash regularly, particularly in back-projection. Available engines in Java were also examined and found to be lacking in support and have quality issues. Also, Java is more challenging to support than other options.
Proj4
The main projection engine used by GIS applications and utilities is Proj4. Proj4 supports the largest number of projections and projection parameters and is fast. Proj4 is available as a C++ open source library, as DLLs, and as a library for Python.
Performance
A web service that called a Python script running Proj4 was investigated and the performance was good for small numbers of coordinates but not large numbers required for complex maps and projection on the fly. A gridded approach where the corners of a grid are projected and sent to the browser was found to have excellent performance. Coodinates that didi not fall on the corners of the grid were interpolated from the nearest grid coordinates.
Design
The design consists of a JavaScript class that provides projection on the fly in the browser. The user specifies the projection and projection settings. Then, when the first function to project coordinates is called, a projeciton grid of with the specified projection and parameters is requested from the server. A Python script running on the server will then either return an existing projection grid file or create a new one. The projection grid files are in JSON format.
Projection Explorer II
For development, a GUI was created called "Projection Explorer II" (the first Projection Explorer is in the software BlueSpray). This GUI allows the user to evaluate various projections and parameters and their performance.
Within the ProjectionGrid folder that is a JSON file, ProjInfo_NoVar.js, that contains a description of the projection methods and their parameters based on Proj4 (note that the contents varies from one install of Proj4 to another).
Finding the Bounds (the _ComputeEastingsAndNorthings())
One of the key challenges in projecting and back-projecting, is finding the valid bounds of the projection. The valid bounds varies with the projection and its settings. Some projections, such as cylindrical projections, always have a bounds matching the entire world. However, other projections, such as those used for UTM, can have massive distortions, wrap around on themselves, and even overlap with themselves. One of the key features in freeing up our use of projections is finding the valid bounds for a projection.
- Collect the settings for the new projection which includes:
- Projection method, associated settings
- LatNewPole - this is a special setting that triggers Proj4s NewPole setting
- LatMin, LatMax, LonMin, LonMax
- LatStep, LonStep - desired increment for the lat/lon grids. The actual step will be close to this but will perfectly divide the distance between the LatMin/LatMax and the LonMin/LonMax
- Initialize the:
- Eastings[][] and Northings[][] grids with nans.
- CellStatues[][] to be STATUS_UNKNOWN
- BoundsPoints[][] to None
- Initialize the Eastings[][] and Northings[][] grids with projected coordinate values. Some of these values will still be nans.
- Finds the EastingMin, EastingMax, NorthingMin, NorthingMax
- Selecting the cell to start finding the bounds
- Currently just finds the middle cell
- Needs to:
- Find the center from the min/max lat/lon
- Adjust if the pole is moved too far
- Add cells that are "INSIDE" the projection bounds
Determining if a Cell is Inside the Projection Bounds (CheckCell())
A cell is marked as inside if:
- It is a valid cell (i.e. row and column are in the grids)
- Area distortion is within allowable limits
- Find the number of sides that are outside the current bounds and are consecutive (should be 0, 1, 2, or 3). Also find the index to the first outside cell. This is a bit tricky:
- the arrays ColumnPointOffsets and RowPointOffsets provides offsets to the cells that are adjacent to the target cell. The indexes are setup to have us go around the target cell up to two times for the algorithm below.
- Find the first adjacent cell that is INSIDE
- Find the next adjacent cell that is OUTISDE (StartOutsideIndex)
- Count the number of OUTSIDE cells
- Check if any of the points between the sides that are common boundaries with an OUTSIDE or UNKNOWN cell are insdie the existing projection bounds.
- If the target cell passed all the checks, take action based on the number of sides:
- Remove the points that form the "notch"
- Move the point between the two sides to add the cell to the bounds
- Add two new points
Checking Cells
Performance
The main class, SPProjectorGrid uses the approach of only computing information that is needed when requested. Then, when settings change, a Reset() function is called to reset all the information that has been computed.
Implementation
The main library for projecting data is SPReferencing in SPPy
To setup the create the grid files:
- Python 3.x can be installed using Anaconda or Mike's wheel files
- Proj4 needs to be installed
- pyproj needs to be added to the Python install
Creating the Grids
The following code is in the C:\ProjectsPython\
- The projection codes and parameter values are dumped from Proj4 to an "output.txt" file in the command line
- ProjInfoToCSV.py converts the output.txt file to ProjInfoRAW.csv
- This file was then manually edited to add:
- Missing parameters
- Valid ranges for lat and lon
- Mark projections as Yes to use if they appeared to work well in both fward and backward projection and where general in use
Note that o_lat_p (Latitude of New Pole) is supported on almost all projections and allows a view of the poles.
Python Classes
SPBoundsPoint - an individual point within the bounds polygon
Member Variables:
- Col
- Row
- Easting
- Northing
- Previous
- Next
SPProjectedBounds - a linked list of STBoundsPoint objects describing the current bounds polygon
Member Variables:
- FirstPoint
- NumPoints
Member Functions:
- Insert(PreviousPoint,NewPoint)
- GetNumPoints() - returns the number of points in the bounds
- GetFirstPoint()
- PointInBounds(X,Y) - returns true if the specified point is in the bounds of the polygon
SPProjectorGrid - the projector class that provides grids of projected points.
Settings:
- LonMin, LonMax - range of longitude values for the grid
- LatMin, LatMax - range of latitude values for the grid
- LonStep, LatStep - spacing between the grid points
Member Variables:
- Eastings[Row][Col] - grid of easting values (projected), nan is used when the value is undefined
- Northings[Row][Col] - grid of northing values (projected), nan is used when the value is undefined
- Longitudes[Row][Col] - grid of longitudes for back projecting from the projected grids.
- Latitudes[Row][Col]
- AreaDistortions[Row][Col] - grid of area distortions for each cell. Only allocated when requested or needed to compute the bounds. Note that there are -1 rows and columns because this grid refers to cells.
- Statuses[Row][Col] - status of each cell as to wether is is inside or outside the valid bounds of the current projection. Options include:
- STATUS_UNKNOWN
- STATUS_INSIDE
- STATUS_OUTSIDE
- ProjectedBounds - object containing the bounds of the projection
- NumCols,NumRows - number of rows and columns in the Eastings and Northings grids.
- EastingMin,EastingMax - range of eastings in the projection grids
- NorthingMin,NorthingMax - range of northings in the projection grids
- LongitudeMax, LongitudeMin
- LatitudeMax, LatitudeMin
- BoundsPoints[][]- reference to the bounds point for this corner of the cell or None
Member Functions:
- Reset() - resets all of the member varioables
- GetAreaDistortion() - returns the grid of area distortions. Creates the grid if needed. Allocates and fills in the AreaDistortions[][] grid
- GetLatLonGrids() - computes the back-projection grids from the projected grids
- ComputeEastingsAndNorthings() - key function that computes the eastings and northings
ComputeEastingsAndNorthings
- Setup local variables
- Initialize the grids: Eastings (nan), Northings (nan), CellStatuses (STATUS_UNKNOWN), BoundsPoints (None)
- Compute the eastings and northings
- Any invalid values are converted to "nan"
- This also initializes:
- NumCols
- NumRows
- LonStep, LatStep
- EastingMax,EastingMin
- NorthingMax, NorthingMin
- Populate the CellStatuses[][] grid
JavaScript Classes
Project Explorer Web Tool
Low-level utilities
- GetSelectedProjectionLibrary()
- AddOptionToSelect(TheSelectElement,TheOptionName,TheOptionValue)
- ZoomToProjectorBounds()
- UpdateLayerVisibility
- UpdateGeoBackgroundVisibility
- ProjectionChanged()
- Layer_ProjectionUncertinaty.Reset();
- TheProjector.SetProjectionGrid(null);
- ProjectorGridRecieved()
- ZoomToBounds();
- TheMainContainer.GetScene().Repaint();
Geographic Map Functions
- ShowSettingsFromGeographicMap() - just pulls settings from geographic map and displays them
- GetSettingsFromGeographicMap()
- ProjectionChanged()
- ShowSettingsFromGeographicMap();
Control Panel Handling
- SetLonLatControlValues(LonMin,LonMax,LatMin,LatMax)
- sets the lat/lon min/max in the geographic map
- GeographicLayer_Bounds.SetBounds([LonMin,LonMax],[LatMin,LatMax]);
- TheProjector.SetSetting("Projection","LonMax",LonMax); - for all lat/lon min/max
- UpdateParameterControls() - sets up the controls for the selected method
- Called when control value changes:
- SetLonLatControlValues(...);
- TheProjector.SetSetting("Projection",ControlName,TheValue);
- ProjectionChanged();
- Called when control value changes:
- ProjectionMethodChanged()
-
- SetLonLatControlValues()
- UpdateParameterControls();
- ProjectionChanged();
- UpdateProjectionMethods()
- ProjectionMethodChanged()
- ProjectionTypeChanged()
- TheProjector.SetSetting("Projection","Type",TypeName);
- UpdateProjectionMethods();
- ProjectionLibraryChanged();
- Sets new projector in the Scene and Geo
- ProjectorGridRecieved(); - called from projector when grid recieved
- ProjectionTypeChanged();
- Sets new projector in the Scene and Geo
Animation
- SetLongitude(TheValue)
- TheProjector.SetSettings({"Projection":{"lon_0":TheValue}});
- GeographicLayer_Bounds.SetLongitudeOfOrigin(TheValue);
- ShowSettingsFromGeographicMap();
- ChangeLongitude(Amount)
- SetLongitude(TheValue);
- ShowSettingsFromGeographicMap();
- Go()
- ChangeLongitude(5)
- OnLoad()
- GeographicLayer_Bounds.MouseUp
- SetLongitude(this.GetLongitudeOfOrigin();
- GetSettingsFromGeographicMap();
- GeographicLayer_Bounds.MouseMove()
- ShowSettingsFromGeographicMap();
- ProjectionLibraryChanged();
- ShowSettingsFromGeographicMap();
- GeographicLayer_Bounds.MouseUp