GithubHelp home page GithubHelp logo

tuw-geo / equi7grid Goto Github PK

View Code? Open in Web Editor NEW
39.0 9.0 12.0 57.72 MB

Definition of the Equi7Grid - a spatial reference optimized for global high-resolution raster data.

License: MIT License

Python 98.10% Makefile 1.90%

equi7grid's Introduction

Equi7Grid

build ubuntu build windows coverage pypi package documentation

The Equi7Grid is a spatial reference system designed to handle efficiently the archiving, processing, and displaying of high resolution raster image data. It supports geo-datacubes holding large volumes of satellite imagery, as it preserves geometric accuracy and minimises data oversampling over global land surfaces to a very low value of 3%.


plot

This package contains:

  • Geometries and projection-files defining the contentinal zones, coordinate system, projection parameters, base tilings, etc.
  • A python class for working with Equi7Grid: how to convert to, how to use the tiling system, how to identify coordinates, etc.

A detailed documentation on the Equi7Grid definition is at

~/docs/doc_files/

and its scientific background is published in this journal article.

News

2024 May:

For the seven continental Equi7 coordinate systems, the newly available EPSG codes EPSG:27701 - EPSG:27707 are available via

  • with proj>=9.4.0 from the generic coordinate transformation software proj (e.g. used within GDAL).
  • with EPSG>=v11.002 from the Geodetic Parameter Dataset of EPSG.

Several updates are in the pipeline of this python package:

  • interface to the EPSG codes
  • updates on the continental zone bordes - streamlining along political delimiters
  • flexible tile extents and grid samplings, allowing also user-defined tile extents
  • updated interfaces to reprojection methods (e.g. to and from UTM, or LonLat)

Geometries

Easiest access to Equi7's seven continental coordinate reference systems (CRSs) is via the EPSG codes:

Africa         EPSG:27701
Antarctica     EPSG:27702
Asia           EPSG:27703
Europe         EPSG:27704
North America  EPSG:27705
Oceania        EPSG:27706
South America  EPSG:27707

Shapefiles for the continental zone boundaries and tilings are here

~/src/equi7grid/grids/

... with files in PROJ projected to the Equi7Grid space (meters), and in GEOG corresponding files in common geographic Lon-Lat space (degrees):

Overlays for visualisation in Google Earth are here:

~/docs/doc_files/google_earth_overlays/

The 7 projections (or more precisely the Projected Coordinate Reference Systems, PROJCS) are completely defined by WKT-strings in the .prj-files at

~/wkt/

or simply by following proj4-strings:

AF: '+proj=aeqd +lat_0=8.5 +lon_0=21.5 +x_0=5621452.01998 +y_0=5990638.42298 +datum=WGS84 +units=m +no_defs'
AN: '+proj=aeqd +lat_0=-90 +lon_0=0 +x_0=3714266.97719 +y_0=3402016.50625 +datum=WGS84 +units=m +no_defs'
AS: '+proj=aeqd +lat_0=47 +lon_0=94 +x_0=4340913.84808 +y_0=4812712.92347 +datum=WGS84 +units=m +no_defs'
EU: '+proj=aeqd +lat_0=53 +lon_0=24 +x_0=5837287.81977 +y_0=2121415.69617 +datum=WGS84 +units=m +no_defs'
NA: '+proj=aeqd +lat_0=52 +lon_0=-97.5 +x_0=8264722.17686 +y_0=4867518.35323 +datum=WGS84 +units=m +no_defs'
OC: '+proj=aeqd +lat_0=-19.5 +lon_0=131.5 +x_0=6988408.5356 +y_0=7654884.53733 +datum=WGS84 +units=m +no_defs'
SA: '+proj=aeqd +lat_0=-14 +lon_0=-60.5 +x_0=7257179.23559 +y_0=5592024.44605 +datum=WGS84 +units=m +no_defs'

User installation

This package can be installed through pip:

pip install Equi7Grid

Installs for scipy and gdal are required from conda or conda-forge (see below how to set up a fresh environment).

Usage examples

The Equi7Grid package provides python tools to interact with different projections, query information from the Equi7Grid geometries, and access the tiling system.

Example 1: Retrieving Equi7 tiles covering a region of interest

You can retrieve all tiles covering a region of interest defined using Lon-Lat coordinates using search_tiles_in_roi:

tiles = Equi7Grid(500).search_tiles_in_roi(bbox=[(0, 30), (10, 40)], coverland=True)
assert sorted(tiles) == sorted([
    'EU500M_E036N006T6', 'EU500M_E042N000T6', 'EU500M_E042N006T6',
    'AF500M_E030N084T6', 'AF500M_E030N090T6', 'AF500M_E036N084T6',
    'AF500M_E036N090T6', 'AF500M_E042N084T6', 'AF500M_E042N090T6'])

snippet source | anchor

Example 2: Reproject a GeoTIFF file to Equi7 tiles

The package provides with the image2equi7grid() a convenient method to quickly convert existing raster data stored as GeoTIFFs to tiles in Equi7Grid projection(s):

input_file = input_dir / "lake_in_russia_lonlat.tif"
image2equi7grid(Equi7Grid(100), input_file.as_posix(), out_dir.as_posix())

assert (out_dir / "EQUI7_AS100M/E018N066T6/lake_in_russia_lonlat_AS100M_E018N066T6.tif").exists()
assert (out_dir / "EQUI7_EU100M/E072N030T6/lake_in_russia_lonlat_EU100M_E072N030T6.tif").exists()

snippet source | anchor

The tool uses gdal to efficiently warp the raster data to the Equi7 projection, and generate a folder structure for each Equi7 tile that covers the input raster.

Note 1: The input file of this (advanced) example lies between Asia and Europe, and by default the function writes output for all tiles that cover the input, in this example for the Equi7 tiles EU100M_E072N030T6 and AS100M_E018N066T6. Checkout the function image2equi7grid() for more options on output, format, encoding, etc.

Note 2: Windows users might need to manually specify the gdal_path as part of the function arguments, for example:

image2equi7grid(gdal_path=r"C:\...your_path...\envs\equi7grid\Library\bin")

Example 3: Reproject Equi7 tiles to the Longitude-Latitude projection

With equi7_to_lonlat(), a simple but convenient method is available to quickly convert files that are already gridded and tiled in the Equi7Grid. Please see the following usage example:

equi7_to_lonlat(roi=(9, 46, 18, 50),
                pixelsize=20,
                input_folder_path = r'C:\...your_main_path...\EQUI7_SUBGRID\TILE',
                input_file_path = 'your_dataset_SUBGRID_TILE.tif',
                full_output_path = r'C:\...your_main_path...\your_dataset_46-50LON_9-18LAT.tif',
                gdal_path = r'C:\...your_GDAL_path...\Library\bin',
                ...
                )

snippet source | anchor

More examples ...

checkout the tests at

~/tests/test_equi7grid.py

which exemplify many more functions.

Development installation

For development, we recommend using the make tool to automatically create python environments and install more complex dependencies i.e. gdal.

Instruction on how to set up an environment on systems without proper make support, such as Windows, can be found in a subsequent section.

Creating python environments with make

Conda

Make sure miniconda3 is installed by following the official installation instructions. To create a new development environment using conda make the conda rule:

make conda

This will create a new conda environment called equi7grid and install all necessary dependencies.

Virtualenv

Make sure you have installed virtualenv and gdal on your system. For instance under Ubuntu you can install gdal using apt install libgdal-dev, and virtualenv using apt install python3-venv. To set up a virtualenv environment simply make the venv rule:

make venv

This will create a virtualenv environment within a venv folder at the root of the project. It will install the gdal dependency using pygdal which requires gdal to be installed on the system.

Installing test dependencies

To create a testing environment you can set the TEST_ENV=1 parameter:

make venv TEST_ENV=1

After activating the environment you can make the test rule to run all unit tests:

make test

Creating python environments on Windows

First make sure miniconda3 is installed on your system by following the installation instructions.

Create the equi7grid conda environment from the environment.yml provided at the root of the repository.

conda env create -f environment_win.yml

See also the official anaconda documentation for detailed instructions on environments and environment files.

Now you should be able to activate the environment:

conda activate equi7grid

Once activate, you can install the Equi7Grid package in development mode using pip by running the following command in the root directory of the repository:

pip install -e .

Installing test dependencies

To install the test dependencies as well use:

pip install -e .[testing]

Now you should be able to run all unit tests:

pytest tests/

You can also have a look at the source of the Makefile for more detailed installation and testing options.

Contribute

We are happy if you want to contribute. Please raise an issue explaining what is missing or if you find a bug. We will also gladly accept pull requests against our master branch for new features or bug fixes.

Guidelines

If you want to contribute please follow these steps:

  • Fork the Equi7Grid repository to your account
  • Clone the repository
  • make a new feature branch from the Equi7Grid master branch
  • Add your feature
  • Please include tests for your contributions in one of the test directories. We use py.test so a simple function called test_my_feature is enough
  • submit a pull request to our master branch

Citation

citation

If you use the software in a publication then please cite it using the Zenodo DOI. Be aware that this badge links to the latest package version.

Please select your specific version at https://doi.org/10.5281/zenodo.1048530 to get the DOI of that version. You should normally always use the DOI for the specific version of your record in citations. This is to ensure that other researchers can access the exact research artefact you used for reproducibility.

You can find additional information regarding DOI versioning at http://help.zenodo.org/#versioning

equi7grid's People

Contributors

actions-user avatar bbauerma avatar cnavacch avatar mazweg avatar sebhahn avatar senmao avatar shochsto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

equi7grid's Issues

The projection WKT files seem to be incomplete

I am working with the EQUI7 grid and the defined Azimuthal_Equidistant projection and seem to be unable to fully transform coordinates properly. I have traced the error back and found that it is unable to convert the file from WKT to PROJ4.

The following code works with input a) but not b). b) seems to be defined here in the project but is missing AXIS and AUTHORITY info from a).

from osgeo import osr

srs = osr.SpatialReference()
srs.ImportFromWkt(PROJ)
print(srs.ExportToProj4())

a)

PROJ = 'PROJCS["Azimuthal_Equidistant",GEOGCS["WGS 84",DATUM["World Geodetic System 1984",SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],UNIT["degree", 0.017453292519943295],AXIS["Geodetic longitude", EAST],AXIS["Geodetic latitude", NORTH],AUTHORITY["EPSG","4326"]],PROJECTION["Azimuthal_Equidistant"],PARAMETER["longitude_of_center", 24.0],PARAMETER["latitude_of_center", 53.0],PARAMETER["false_easting", 5837287.81977],PARAMETER["false_northing", 2121415.69617],UNIT["m", 1.0],AXIS["Easting", EAST],AXIS["Northing", NORTH]]'

b)

PROJ = 'PROJCS["Azimuthal_Equidistant",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984", SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich ",0.0],UNIT["Degree",0.017453292519943295]],PROJECTION[" Azimuthal_Equidistant"],PARAMETER["false_easting",6988408.5356], PARAMETER["false_northing",7654884.53733],PARAMETER[" central_meridian",131.5],PARAMETER["latitude_of_origin",-19.5],UNIT ["Meter",1.0]]'

I get the following error with b)

ERROR 6: No translation for  Azimuthal_Equidistant to PROJ.4 format is known.

The original code is below that caused me the issue

from osgeo import osr

def get_geog_coords(geo_points, spatial_ref):
    t = osr.CoordinateTransformation(spatial_ref.CloneGeogCS(), spatial_ref)
    print(t)

    def transform(p):
        x, y, z = t.TransformPoint(p['lat'], p['lon'])
        return {'x': x, 'y': y}

    return {key: transform(p) for key, p in geo_points.items()}

points = {
    'll': {'lat': 47.483612, 'lon': 14.965127},
    'lr': {'lat': 47.483612, 'lon': 18.886929},
    'ul': {'lat': 49.559105, 'lon': 14.965127},
    'ur': {'lat': 49.559105, 'lon': 18.886929}
}

spatial_ref = osr.SpatialReference()
# PROJ is from b) and gathered from the metadatabase @ csw.eodc.eu
spatial_ref.ImportFromWkt(PROJ)

get_geog_coords(points, spatial_ref)

NumPy deprecation warning

This issue causes a warning with Python==3.7 and numpy==1.20 at the moment:

...\lib\site-packages\equi7grid\equi7grid.py:567: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. 
When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. If you wish to review your current use, check the release note link for additional information.
  Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
    np.int(llx) // 100000, np.int(lly) // 100000, tilecode)

The question of the range of decibels(dB)values

I download S1GBM_VV_mean_mosaic_v1_EQUI7_AS010M file. and take some area in china to view.
I find that the range of dB values seems not right.
As my personal understand, the SAR general situation is that the dB value should be around -5 to -17.
The distribution of results I looked at in the documentation(The normalised Sentinel-1 Global Backscatter Model, mapping Earth’s land surface with C-band microwaves.pdf) had the same distribution of values
But I noticed from the downloaded File that the dB values range around -100.
How to solve this please, thank you!
info123

fractional false eastings and northings

is there a good reason for these values being so specific? e.g. for AF

+x_0=5621452.01998 +y_0=5990638.42298

I feel like they should at least be whole integers, but also why not have clean whole chunkier numbers like 5630000 and 6000000 ? (Perhaps they are derived from some deeper grain defined in the longitude/latitude grid scheme? )

pyproj depreciation warning for crs-specification

from equi7grid.equi7grid import Equi7Grid
grid = Equi7Grid(500).subgrids["EU"]
subgrid.xy2lonlat(3740750.0, 2554250.0)
...\lib\site-packages\pyproj\crs\crs.py:131: FutureWarning: '+init=<authority>:<code>' syntax is deprecated. '<authority>:<code>' is the preferred initialization method. When making the change, be mindful of axis order changes: https://pyproj4.github.io/pyproj/stable/gotchas.html#axis-order-changes-in-proj-6
  in_crs_string = _prepare_from_proj_string(in_crs_string)

equi7grid package not working with numpy >= 1.24.0

Dear all,

np.int does not work anymore with the latest numpy version (1.24.0). See the deprecation warning below when using numpy==1.20.3.

site-packages/equi7grid/equi7grid.py:557: DeprecationWarning: `np.int` is a deprecated alias for the builtin `int`. 
To silence this warning, use `int` by itself. Doing this will not modify any behavior and is safe. 
When replacing `np.int`, you may wish to use e.g. `np.int64` or `np.int32` to specify the precision. 
If you wish to review your current use, check the release note link for additional information.
  Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
    np.int(llx) // 100000,

Exception traceback when using numpy version 1.24.0

e7tile = e7grid.create_tile(e7t)
  File "/venv/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 278, in create_tile
    return self.subgrids[name[0:2]].tilesys.create_tile(name)
  File "/venv/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 505, in create_tile
    name = self._encode_tilename(llx, lly)
  File "/venv/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 596, in _encode_tilename
    return self.encode_tilename(llx, lly, self.core.sampling, self.core.tiletype, shortform=shortform)
  File "/venv/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 567, in encode_tilename
    np.int(llx) // 100000, np.int(lly) // 100000, tilecode)
  File "/venv/lib/python3.9/site-packages/numpy/__init__.py", line 284, in __getattr__
    raise AttributeError("module {!r} has no attribute "
AttributeError: module 'numpy' has no attribute 'int'

Cannot import Equi7Grid after pip install v0.2.3

Just leaving this here in case someone else tries to install the latest version of the package.
Looks like the data folder is not downloaded correctly in version 0.2.3.

I get the following error on import

>>> from equi7grid.equi7grid import Equi7grid
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wpreimes/mambaforge/envs/io_utils/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 74, in <module>
    class Equi7Grid(TiledProjectionSystem):
  File "/home/wpreimes/mambaforge/envs/io_utils/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 95, in Equi7Grid
    _static_data = _load_static_data(__file__)
  File "/home/wpreimes/mambaforge/envs/io_utils/lib/python3.9/site-packages/equi7grid/equi7grid.py", line 69, in _load_static_data
    with open(fname, "rb") as f:
FileNotFoundError: [Errno 2] No such file or directory: '/home/wpreimes/mambaforge/envs/io_utils/lib/python3.9/site-packages/equi7grid/data/equi7grid.dat'

Workaround is to pip install equi7grid==0.2.2

However it should be easy to fix this. I assume something to do with the meta-package. Just let me know if you cannot work on this right now, I can take a look if you want.

as a USER, I want to use different tile sizes

  • allow different tilings, other than the existing T6, T3, T1. This may be squares of 3km, 10km, 50km, 1000km, etc, and may be defined by the user
    DON'T allow tile sizes and samplings that do no divide without remainder
  • re-define standard set of tilings, which is proposed to the users, and is provided by us developers
  • perhaps: replace current definitions by shapefiles by set of rules
  • perhaps: make use of https://github.com/TUW-GEO/geospade

update static data

in the python_static_data: extents of continents are not clean polygons (update with shapes from v14)

avoid using pkg_resources to fetch version number

It seems that at the moment, 91% of the import-time for import equi7grid is due to the pkg_resources package whose only task is to identify the version number...

This performance penalty is well known for that package... I'd suggest using a different way to identify the version-number...

import pkg_resources
try:
__version__ = pkg_resources.get_distribution(__name__).version
except:
__version__ = 'unknown'

here's a quick check of the relevant import-times:

Is the project alive?

Hi, I came across this project when searching for unified tiling system to be implemented in our company. In on of the pull requests, @bbauerma mentioned the team is working on a rewrite which would make the tile size more configurable. I am wondering, is the project is still alive?
Thx

mixup in doc of "tile.xy2ij" and "tile.ij2xy"

@bbauerma
there's some mixup in the documentation of the tile-functions tile.xy2ij and tile.ij2xy

  • the doc of tile.xy2ij says it returns (COLUMN, ROW)
  • the doc of tile.ij2xy says its arguments are (ROW, COLUMN)
    (I guess this is wrong...)

...if the docs would be correct, this should not be true:

from equi7grid import equi7grid
grid = equi7grid.Equi7Grid(500)
tile = grid.create_tile('EU500M_E048N012T6')
(4873500, 1233000) == tile.ij2xy(*tile.xy2ij(4873500, 1233000))
>>> True

Failed to reample an image to Equi7Grid with image2equi7grid function

The image2equi7grid failed when resampling an image with non-lat/lon projectino to Equi7 Grid. It returns the below error.

====

ERROR 6: Incompatible geometry for operation
2019-10-04 15:30:42 [ERROR]: File-not-processed: S1B_EW_GRDH_1SDH_20191002T214344_20191002T214444_018302_02279D_7471.zip
Traceback (most recent call last):
File "/home/scao/shares/disk/swdvlp/git_eodc/A01-preprocessing/sgrt_A01/A01_workflow_sar_full_preprocessing.py", line 166, in A01_workflow_sar_full_preprocessing
resample_to_equi7(sar_ds, job.wflow_id,
File "/home/scao/shares/disk/swdvlp/git_eodc/A01-preprocessing/sgrt_A01/A01_workflow_sar_full_preprocessing.py", line 295, in resample_to_equi7
tiledtiff=tiled_tif, blocksize=block_size)
File "/home/scao/shares/disk/swdvlp/git_eodc/Equi7Grid/equi7grid/image2equi7grid.py", line 314, in image2equi7grid
subgrid_ids=subgrid_ids)
File "/home/scao/shares/disk/swdvlp/git_eodc/pytileproj/pytileproj/base.py", line 505, in search_tiles_in_roi
overlapped_grids = self.locate_geometry_in_subgrids(roi_geometry)
File "/home/scao/shares/disk/swdvlp/git_eodc/pytileproj/pytileproj/base.py", line 240, in locate_geometry_in_subgrids
if ptpgeometry.check_lonlat_intersection(geometry, self.subgrids.get(x).polygon_geog):
File "/home/scao/shares/disk/swdvlp/git_eodc/pytileproj/pytileproj/geometry.py", line 428, in check_lonlat_intersection
area = get_lonlat_intersection(geometry1, geometry2)
File "/home/scao/shares/disk/swdvlp/git_eodc/pytileproj/pytileproj/geometry.py", line 462, in get_lonlat_intersection
polygons = split_polygon_by_antimeridian(geometry1c)
File "/home/scao/shares/disk/swdvlp/git_eodc/pytileproj/pytileproj/geometry.py", line 489, in split_polygon_by_antimeridian
lons = [p[0] for p in in_points]
TypeError: 'NoneType' object is not iterable

Confusing results using search_tiles_in_roi and get_tile_bbox_proj

When searching tiles, it seems like we need to swap x and y to get the correct tile (We get AF instead of EU otherwise).
Further, when searching, we can specify CRS while the bbox for tiles is given in ?? metres??? or something ?

Is this a bug or should I use some other function to get the tile bbox in e.g EPSG:3006 ?

Output from the experiment with and without swapped x-y for EPSG3006 and EPSG4326:

 ??       left    lower     right   upper
             west,   south,    east,   north
            13.31,   59.09    14.11,   59.44
           403050, 6551299   449522  6589385
 -------  Non Swapped ---------------------
['AF300M_E096N066T6']
EPSG: 4326
ODC [[13.31, 59.09], [14.11, 59.44]]
EQ7 (9600000.0, 6600000.0, 10200000.0, 7200000.0)

['AF300M_E102N054T6']
EPSG: 3006
ODC [[403050.0, 6550350.0], [449550.0, 6590250.0]]
EQ7 (10200000.0, 5400000.0, 10800000.0, 6000000.0)

 -------   Swapped ---------------------
['EU300M_E048N024T6']
EPSG: 4326
ODC [[59.09, 13.31], [59.44, 14.11]]
EQ7 (4800000.0, 2400000.0, 5400000.0, 3000000.0)

['EU300M_E048N024T6']
EPSG: 3006
ODC [[6550350, 403050], [6590250, 449550]]
EQ7 (4800000.0, 2400000.0, 5400000.0, 3000000.0)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.