├── .gitignore ├── LICENSE ├── README.md ├── rpcm ├── __about__.py ├── __init__.py ├── cli.py ├── geo.py ├── rpc_file_readers.py ├── rpc_model.py └── utils.py ├── setup.py └── tests ├── aoi.geojson ├── test_localization.py ├── test_rpc_comparison.py ├── test_rpc_constructors.py ├── test_rpc_file_parsing.py ├── test_rpc_files ├── 20191015_073816_ssc1d3_0011_basic_l1a_panchromatic_dn_RPC.TXT ├── rpc_IKONOS.txt ├── rpc_PLANET_L1A.txt ├── rpc_PLANET_L1B.txt ├── rpc_PLEIADES.xml ├── rpc_SPOT6.xml ├── rpc_WV1.xml ├── rpc_WV2.xml ├── rpc_WV3.xml └── rpc_unsupported.xml └── tests.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist/ 3 | *.egg-info/ 4 | .ipynb_checkpoints 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2015, Centre Borelli 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RPCM - Rational Polynomial Camera Model 2 | 3 | Python implementation of the Rational Polynomial Camera (RPC) model for optical satellite images. 4 | 5 | [Carlo de Franchis](mailto:carlo.de-franchis@ens-paris-saclay.fr), Gabriele Facciolo, Enric Meinhardt-Llopis 6 | (Centre Borelli, ENS Paris-Saclay, Université Paris-Saclay) 2013-21 7 | 8 | `rpcm` is a Python library and command line tool for geolocating satellite images 9 | with RPCs. Its main source code repository is https://github.com/centreborelli/rpcm. 10 | 11 | 12 | # Installation 13 | 14 | To install `rpcm` from PyPI: 15 | 16 | pip install rpcm 17 | 18 | Alternatively, to install `rpcm` from sources: 19 | 20 | git clone https://github.com/centreborelli/rpcm.git 21 | cd rpcm 22 | pip install -e . 23 | 24 | 25 | # Usage 26 | 27 | `rpcm` is a Python library that can be imported into other applications. A 28 | Jupyter notebook tutorial covering the main features of the library is included (TODO). 29 | 30 | `rpcm` also comes with a Command Line Interface (CLI). The `rpcm` CLI has an 31 | extensive help that can be printed with the `-h` and `--help` switches. 32 | 33 | $ rpcm -h 34 | usage: rpcm [-h] {projection, localization, crop, footprint, angle} ... 35 | 36 | Some CLI usage examples can be found in `tests/tests.sh`. 37 | 38 | There are several subcommands, `projection`, `localization`, `crop`, 39 | `footprint`, `angle`, each of which has its own help. 40 | 41 | 42 | ## Projection 43 | 44 | $ rpcm projection -h 45 | usage: rpcm projection [-h] [--lon LON] [--lat LAT] [-z Z] [--points POINTS] 46 | [--crop CROP] [--svg SVG] 47 | img 48 | 49 | positional arguments: 50 | img path or url to a GeoTIFF image file with RPC metadata 51 | 52 | optional arguments: 53 | -h, --help show this help message and exit 54 | --lon LON longitude 55 | --lat LAT latitude 56 | -z Z altitude, in meters 57 | --points POINTS path to a 2/3 columns txt file: lon lat [z] 58 | --crop CROP path to a tif crop previously produced by rpcm. Image 59 | coordinates are computed with respect to this crop. 60 | --svg SVG path to an svg file where to plot projected points. 61 | 62 | 63 | ## Localization 64 | 65 | $ rpcm localization -h 66 | usage: rpcm localization [-h] [-x X] [-y Y] [-z Z] [--points POINTS] 67 | [--crop CROP] 68 | img 69 | 70 | positional arguments: 71 | img path or url to a GeoTIFF image file with RPC metadata 72 | 73 | optional arguments: 74 | -h, --help show this help message and exit 75 | -x X horizontal pixel coordinate (i.e. column index) 76 | -y Y vertical pixel coordinate (i.e. row index) 77 | -z Z altitude, in meters 78 | --points POINTS path to a 3 columns txt file: x y z 79 | --crop CROP path to a tif crop previously produced by rpcm. Image 80 | coordinates are interpreted with respect to this crop. 81 | 82 | 83 | ## Crop 84 | 85 | $ rpcm crop -h 86 | usage: rpcm crop [-h] [-z Z] img aoi crop 87 | 88 | positional arguments: 89 | img path to a GeoTIFF image file with RPC metadata 90 | aoi path to geojson file defining the area of interest (AOI) 91 | crop path to the output cropped tif image 92 | 93 | optional arguments: 94 | -h, --help show this help message and exit 95 | -z Z altitude of the crop center 96 | 97 | 98 | ## Footprint 99 | 100 | $ rpcm footprint -h 101 | usage: rpcm footprint [-h] [-z Z] img 102 | 103 | positional arguments: 104 | img path or url to a GeoTIFF image file with RPC metadata 105 | 106 | optional arguments: 107 | -h, --help show this help message and exit 108 | -z Z altitude, in meters 109 | 110 | 111 | ## Angle 112 | 113 | $ rpcm angle -h 114 | usage: rpcm angle [-h] [--lon LON] [--lat LAT] [-z Z] img1 img2 115 | 116 | positional arguments: 117 | img1 path to a GeoTIFF image file with RPC metadata 118 | img2 path to a GeoTIFF image file with RPC metadata 119 | 120 | optional arguments: 121 | -h, --help show this help message and exit 122 | --lon LON longitude 123 | --lat LAT latitude 124 | -z Z altitude 125 | 126 | 127 | # Common issues 128 | 129 | _Warning_: A `rasterio` issue on Ubuntu causes the need for this environment 130 | variable (more info on [rasterio's 131 | github](https://github.com/mapbox/rasterio/issues/942)): 132 | 133 | export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt 134 | -------------------------------------------------------------------------------- /rpcm/__about__.py: -------------------------------------------------------------------------------- 1 | __title__ = "rpcm" 2 | __description__ = "Rational Polynomial Camera Model for optical satellite images." 3 | __url__ = 'https://github.com/centreborelli/rpcm' 4 | __version__ = "1.4.10" 5 | __author__ = "Carlo de Franchis" 6 | __author_email__ = 'carlo.de-franchis@ens-paris-saclay.fr' 7 | -------------------------------------------------------------------------------- /rpcm/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | import geojson 5 | import numpy as np 6 | import rasterio 7 | import rasterio.warp 8 | import srtm4 9 | 10 | from rpcm import rpc_model 11 | from rpcm import utils 12 | from rpcm.rpc_model import RPCModel 13 | from rpcm.rpc_model import rpc_from_geotiff 14 | from rpcm.rpc_model import rpc_from_rpc_file 15 | from rpcm.__about__ import __version__ 16 | 17 | 18 | warnings.filterwarnings("ignore", 19 | category=rasterio.errors.NotGeoreferencedWarning) 20 | 21 | 22 | class NotGeoreferencedError(Exception): 23 | """ 24 | Custom rpcm Exception. 25 | """ 26 | pass 27 | 28 | 29 | class NoSRTMWarning(Warning): 30 | """ 31 | Custom rpcm warning raised when no SRTM altitude is available. 32 | """ 33 | pass 34 | 35 | 36 | def projection(img_path, lon, lat, z=None, crop_path=None, svg_path=None, 37 | verbose=False): 38 | """ 39 | Conversion of geographic coordinates to image coordinates. 40 | 41 | Args: 42 | img_path (str): path or url to a GeoTIFF image with RPC metadata 43 | lon (float or list): longitude(s) of the input points 44 | lat (float or list): latitude(s) of the input points 45 | z (float or list): altitude(s) of the input points 46 | crop_path (str): path or url to an image crop produced by rpcm. 47 | Projected image coordinates are computed wrt this crop. 48 | svg_path (str): path to an svg file where to plot the projected image 49 | point(s) 50 | 51 | Returns: 52 | float or list: x pixel coordinate(s) of the projected point(s) 53 | float or list: y pixel coordinate(s) of the projected point(s) 54 | """ 55 | rpc = rpc_from_geotiff(img_path) 56 | if z is None: 57 | z = srtm4.srtm4(lon, lat) 58 | 59 | x, y = rpc.projection(lon, lat, z) 60 | 61 | if crop_path: # load and apply crop transformation matrix 62 | with rasterio.open(crop_path, 'r') as src: 63 | tags = src.tags() 64 | 65 | C = list(map(float, tags['CROP_TRANSFORM'].split())) 66 | C = np.array(C).reshape(3, 3) 67 | h = np.row_stack((x, y, x**0)) # homogeneous coordinates 68 | x, y = np.dot(C, h).squeeze()[:2] 69 | 70 | if svg_path: #TODO 71 | pass 72 | 73 | if verbose: 74 | for p in zip(np.atleast_1d(x), np.atleast_1d(y)): 75 | print('{:.4f} {:.4f}'.format(*p)) 76 | 77 | return x, y 78 | 79 | 80 | def localization(img_path, x, y, z, crop_path=None, verbose=False): 81 | """ 82 | Conversion of image coordinates to geographic coordinates. 83 | 84 | Args: 85 | img_path (str): path or url to a GeoTIFF image with RPC metadata 86 | x (float or list): x coordinate(s) of the input points 87 | y (float or list): y coordinate(s) of the input points 88 | z (float or list): altitude(s) of the input points 89 | crop_path (str): path or url to an image crop produced by rpcm. 90 | Input image coordinates are interpreted wrt this crop. 91 | 92 | Returns: 93 | float or list: longitude(s) of the localised point(s) 94 | float or list: latitude(s) of the localised point(s) 95 | """ 96 | if crop_path: # load and apply crop transformation matrix 97 | with rasterio.open(crop_path, 'r') as src: 98 | tags = src.tags() 99 | 100 | C = list(map(float, tags['CROP_TRANSFORM'].split())) 101 | C = np.array(C).reshape(3, 3) 102 | h = np.row_stack((x, y, x**0)) # homogeneous coordinates 103 | x, y = np.dot(np.linalg.inv(C), h).squeeze()[:2] 104 | 105 | rpc = rpc_from_geotiff(img_path) 106 | lon, lat = rpc.localization(x, y, z) 107 | 108 | if verbose: 109 | for p in zip(np.atleast_1d(lon), np.atleast_1d(lat)): 110 | print('{:.8f} {:.8f}'.format(*p)) 111 | 112 | return lon, lat 113 | 114 | 115 | def crop(output_crop_path, input_geotiff_path, aoi, z=None): 116 | """ 117 | Crop an area of interest (AOI) defined with geographic coordinates. 118 | 119 | Args: 120 | output_crop_path (str): path to the output crop file 121 | input_img_path (str): path or url to a GeoTIFF image with RPC metadata 122 | aoi (geojson.Polygon): longitude, latitude vertices of the polygon 123 | defining the AOI. 124 | z (float): altitude of the AOI center with respect to the WGS84 125 | ellipsoid. 126 | """ 127 | if z is None: # read z from srtm 128 | lons, lats = np.asarray(aoi['coordinates']).squeeze().T 129 | z = srtm4.srtm4(np.mean(lons[:-1]), np.mean(lats[:-1])) 130 | 131 | # do the crop 132 | crop, x, y = utils.crop_aoi(input_geotiff_path, aoi, z) 133 | 134 | # crop transform to be stored in the tiff header 135 | T = np.eye(3) 136 | T[0, 2] = -x 137 | T[1, 2] = -y 138 | # TODO update the RPC coefficients to account for the crop 139 | 140 | # write file 141 | tags = {'CROP_TRANSFORM': ' '.join(str(t) for t in T.flatten()), 142 | 'SOFTWARE': 'produced with rpcm {}'.format(__version__)} 143 | utils.rasterio_write(output_crop_path, crop, tags=tags) 144 | 145 | 146 | def image_footprint(geotiff_path, z=None, verbose=False): 147 | """ 148 | Compute the longitude, latitude footprint of an image using its RPC model. 149 | 150 | Args: 151 | geotiff_path (str): path or url to a GeoTIFF file 152 | z (float): altitude (in meters above the WGS84 ellipsoid) used to 153 | convert the image corners pixel coordinates into longitude, latitude 154 | 155 | Returns: 156 | geojson.Feature object containing the image footprint polygon 157 | """ 158 | with rasterio.open(geotiff_path, 'r') as src: 159 | rpc_dict = src.tags(ns='RPC') 160 | h, w = src.shape 161 | 162 | bounds = src.bounds # in case of georeferenced ortho image 163 | crs = src.crs 164 | 165 | if rpc_dict: 166 | rpc = rpc_model.RPCModel(rpc_dict) 167 | if z is None: 168 | z = srtm4.srtm4(rpc.lon_offset, rpc.lat_offset) 169 | if np.isnan(z): 170 | warnings.warn("no SRTM altitude available, using RPC alt_offset", 171 | category=NoSRTMWarning) 172 | z = rpc.alt_offset 173 | 174 | lons, lats = rpc.localization([0, 0, w, w, 0], 175 | [0, h, h, 0, 0], 176 | [z, z, z, z, z]) 177 | 178 | elif crs and bounds: 179 | bounds = rasterio.warp.transform_bounds(crs, {'init': 'epsg:4326'}, 180 | *bounds) 181 | lons = bounds[0], bounds[2], bounds[2], bounds[0], bounds[0] 182 | lats = bounds[1], bounds[1], bounds[3], bounds[3], bounds[1] 183 | 184 | else: 185 | raise NotGeoreferencedError 186 | 187 | footprint = geojson.Feature(geometry=geojson.Polygon([list(zip(lons, lats))]), 188 | properties={"name": os.path.basename(geotiff_path)}) 189 | 190 | if verbose: 191 | print(geojson.dumps(footprint)) 192 | 193 | return footprint 194 | 195 | 196 | def angle_between_views(geotiff_path_1, geotiff_path_2, lon=None, lat=None, 197 | z=None, verbose=False): 198 | """ 199 | Compute the view angle difference between two (stereo) images. 200 | 201 | Args: 202 | geotiff_path_1 (str): path or url to a GeoTIFF file 203 | geotiff_path_2 (str): path or url to a GeoTIFF file 204 | lon, lat, z (floats): longitude, latitude, altitude of the 3D point 205 | where to compute the angle 206 | 207 | Returns: 208 | float: angle between the views, in degrees 209 | """ 210 | rpc1 = rpc_from_geotiff(geotiff_path_1) 211 | rpc2 = rpc_from_geotiff(geotiff_path_2) 212 | 213 | if lon is None: 214 | lon = rpc1.lon_offset 215 | if lat is None: 216 | lat = rpc1.lat_offset 217 | if z is None: 218 | z = srtm4.srtm4(lon, lat) 219 | 220 | a = utils.viewing_direction(*rpc1.incidence_angles(lon, lat, z)) 221 | b = utils.viewing_direction(*rpc2.incidence_angles(lon, lat, z)) 222 | angle = np.degrees(np.arccos(np.dot(a, b))) 223 | 224 | if verbose: 225 | print('{:.3f}'.format(angle)) 226 | 227 | return angle 228 | -------------------------------------------------------------------------------- /rpcm/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import geojson 4 | 5 | import rpcm 6 | 7 | 8 | def valid_geojson(filepath): 9 | """ 10 | Check if a file contains valid geojson. 11 | """ 12 | with open(filepath, 'r') as f: 13 | geo = geojson.load(f) 14 | if type(geo) == geojson.geometry.Polygon: 15 | return geo 16 | if type(geo) == geojson.feature.FeatureCollection: 17 | p = geo['features'][0]['geometry'] 18 | if type(p) == geojson.geometry.Polygon: 19 | return p 20 | raise argparse.ArgumentTypeError('Invalid geojson: only polygons are supported') 21 | 22 | 23 | def main(): 24 | """ 25 | Command line interface for rpcm. 26 | """ 27 | parser = argparse.ArgumentParser(description=('RPC model toolkit')) 28 | subparsers = parser.add_subparsers(dest='cmd', help='projection, localization, crop or footprint', 29 | metavar='{projection, localization, crop, footprint}') 30 | subparsers.required = True 31 | 32 | # parser for the "footprint" command 33 | parser_footprint = subparsers.add_parser('footprint', 34 | help='print the image footprint as a ' 35 | '(lon, lat) polygon') 36 | parser_footprint.add_argument('img', help=('path or url to a GeoTIFF image ' 37 | 'file with RPC metadata')) 38 | parser_footprint.add_argument('-z', type=float, help=('altitude, in meters')) 39 | 40 | # parser for the "projection" command 41 | parser_proj = subparsers.add_parser('projection', 42 | help='conversion of geographic to image' 43 | ' coordinates') 44 | parser_proj.add_argument('img', 45 | help=('path or url to a GeoTIFF image file with RPC metadata')) 46 | parser_proj.add_argument('--lon', type=float, help=('longitude')) 47 | parser_proj.add_argument('--lat', type=float, help=('latitude')) 48 | parser_proj.add_argument('-z', type=float, help=('altitude, in meters')) 49 | parser_proj.add_argument('--points', 50 | help=('path to a 2/3 columns txt file: lon lat [z]')) 51 | parser_proj.add_argument('--crop', type=str, 52 | help=('path to a tif crop previously produced by rpcm. ' 53 | 'Image coordinates are computed with respect ' 54 | 'to this crop.')) 55 | parser_proj.add_argument('--svg', type=str, 56 | help=('path to an svg file where to plot ' 57 | 'projected points.')) 58 | 59 | # parser for the "localization" command 60 | parser_loc = subparsers.add_parser('localization', 61 | help='conversion of image to geographic' 62 | ' coordinates') 63 | parser_loc.add_argument('img', 64 | help=('path or url to a GeoTIFF image file with RPC metadata')) 65 | parser_loc.add_argument('-x', type=float, help=('horizontal pixel coordinate (i.e. column index)')) 66 | parser_loc.add_argument('-y', type=float, help=('vertical pixel coordinate (i.e. row index)')) 67 | parser_loc.add_argument('-z', type=float, help=('altitude, in meters')) 68 | parser_loc.add_argument('--points', 69 | help=('path to a 3 columns txt file: x y z')) 70 | parser_loc.add_argument('--crop', type=str, 71 | help=('path to a tif crop previously produced by rpcm. ' 72 | 'Image coordinates are interpreted with respect ' 73 | 'to this crop.')) 74 | 75 | # parser for the "crop" command 76 | parser_crop = subparsers.add_parser('crop', 77 | help='crop a polygon defined with geographic coordinates') 78 | parser_crop.add_argument('img', 79 | help=('path to a GeoTIFF image file with RPC metadata')) 80 | parser_crop.add_argument('aoi', type=valid_geojson, 81 | help=('path to geojson file defining the area of interest (AOI)')) 82 | parser_crop.add_argument('-z', type=float, 83 | help=('altitude of the crop center')) 84 | parser_crop.add_argument('crop', 85 | help=('path to the output cropped tif image')) 86 | 87 | # parser for the "angle" command 88 | parser_angle = subparsers.add_parser('angle', 89 | help='compute the view angle difference between two (stereo) images') 90 | parser_angle.add_argument('img1', 91 | help=('path to a GeoTIFF image file with RPC metadata')) 92 | parser_angle.add_argument('img2', 93 | help=('path to a GeoTIFF image file with RPC metadata')) 94 | parser_angle.add_argument('--lon', type=float, help=('longitude')) 95 | parser_angle.add_argument('--lat', type=float, help=('latitude')) 96 | parser_angle.add_argument('-z', type=float, help=('altitude')) 97 | 98 | args = parser.parse_args() 99 | 100 | if args.cmd == 'footprint': 101 | rpcm.image_footprint(args.img, args.z, verbose=True) 102 | 103 | elif args.cmd == 'projection': 104 | if args.points and (args.lat is not None or args.lon is not None): 105 | parser.error('--points and {--lat, --lon} are mutually exclusive') 106 | if not args.points and (args.lon is None or args.lat is None): 107 | parser.error('either --points or {--lat, --lon} must be defined') 108 | if args.points: 109 | rpcm.projection(args.img, *np.loadtxt(args.points).T, 110 | crop_path=args.crop, svg_path=args.svg, 111 | verbose=True) 112 | else: 113 | rpcm.projection(args.img, args.lon, args.lat, args.z, 114 | crop_path=args.crop, svg_path=args.svg, 115 | verbose=True) 116 | 117 | elif args.cmd == 'localization': 118 | if args.points and (args.x is not None or args.y is not None or args.z 119 | is not None): 120 | parser.error('--points and {-x, -y, -z} are mutually exclusive') 121 | if not args.points and (args.x is None or args.y is None or args.z is 122 | None): 123 | parser.error('either --points or {-x, -y, -z} must be defined') 124 | if args.points: 125 | rpcm.localization(args.img, *np.loadtxt(args.points).T, 126 | crop_path=args.crop, verbose=True) 127 | else: 128 | rpcm.localization(args.img, args.x, args.y, args.z, 129 | crop_path=args.crop, verbose=True) 130 | 131 | elif args.cmd == 'crop': 132 | rpcm.crop(args.crop, args.img, args.aoi, args.z) 133 | 134 | elif args.cmd == 'angle': 135 | rpcm.angle_between_views(args.img1, args.img2, args.lon, args.lat, 136 | args.z, verbose=True) 137 | 138 | 139 | if __name__ == '__main__': 140 | main() 141 | -------------------------------------------------------------------------------- /rpcm/geo.py: -------------------------------------------------------------------------------- 1 | def compute_epsg(lon, lat): 2 | """ 3 | Compute the EPSG code of the UTM zone which contains 4 | the point with given longitude and latitude 5 | 6 | Args: 7 | lon (float): longitude of the point 8 | lat (float): latitude of the point 9 | 10 | Returns: 11 | int: EPSG code 12 | """ 13 | # UTM zone number starts from 1 at longitude -180, 14 | # and increments by 1 every 6 degrees of longitude 15 | zone = int((lon + 180) // 6 + 1) 16 | 17 | # EPSG = CONST + ZONE where CONST is 18 | # - 32600 for positive latitudes 19 | # - 32700 for negative latitudes 20 | const = 32600 if lat > 0 else 32700 21 | return const + zone 22 | -------------------------------------------------------------------------------- /rpcm/rpc_file_readers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015-19, Carlo de Franchis 2 | # Copyright (C) 2015-19, Gabriele Facciolo 3 | # Copyright (C) 2015-19, Enric Meinhardt 4 | 5 | 6 | from xml.etree import ElementTree 7 | 8 | 9 | def read_rpc_file(rpc_file): 10 | """ 11 | Read RPC from a file deciding the format from the extension of the filename. 12 | xml : spot6, pleiades, worldview 13 | txt (others) : ikonos 14 | 15 | Args: 16 | rpc_file: RPC sidecar file path 17 | 18 | Returns: 19 | dictionary read from the RPC file, or an empty dict if fail 20 | 21 | """ 22 | 23 | with open(rpc_file) as f: 24 | rpc_content = f.read() 25 | 26 | if rpc_file.lower().endswith('xml'): 27 | try: 28 | rpc = read_rpc_xml(rpc_content) 29 | except NotImplementedError: 30 | raise NotImplementedError('XML file {} not supported'.format(rpc_file)) 31 | else: 32 | # we assume that non xml rpc files follow the ikonos convention 33 | rpc = read_rpc_ikonos(rpc_content) 34 | 35 | return rpc 36 | 37 | 38 | def read_rpc_ikonos(rpc_content): 39 | """ 40 | Read RPC file assuming the ikonos format 41 | 42 | Args: 43 | rpc_content: content of RPC sidecar file path read as a string 44 | 45 | Returns: 46 | dictionary read from the RPC file 47 | 48 | """ 49 | import re 50 | 51 | lines = rpc_content.split('\n') 52 | 53 | d = {} 54 | for l in lines: 55 | ll = l.split() 56 | if len(ll) > 1: 57 | k = re.sub(r"[^a-zA-Z0-9_]","",ll[0]) 58 | d[k] = ll[1] 59 | 60 | def parse_coeff(dic, prefix, indices): 61 | """ helper function""" 62 | return ' '.join([dic["%s_%s" % (prefix, str(x))] for x in indices]) 63 | 64 | d['SAMP_NUM_COEFF'] = parse_coeff(d, "SAMP_NUM_COEFF", range(1, 21)) 65 | d['SAMP_DEN_COEFF'] = parse_coeff(d, "SAMP_DEN_COEFF", range(1, 21)) 66 | d['LINE_NUM_COEFF'] = parse_coeff(d, "LINE_NUM_COEFF", range(1, 21)) 67 | d['LINE_DEN_COEFF'] = parse_coeff(d, "LINE_DEN_COEFF", range(1, 21)) 68 | 69 | # if the LON/LAT coefficients are present then it must be an "extended ikonos" 70 | if 'LON_NUM_COEFF_1' in d: 71 | d['LON_NUM_COEFF'] = parse_coeff(d, "LON_NUM_COEFF", range(1, 21)) 72 | d['LON_DEN_COEFF'] = parse_coeff(d, "LON_DEN_COEFF", range(1, 21)) 73 | d['LAT_NUM_COEFF'] = parse_coeff(d, "LAT_NUM_COEFF" , range(1, 21)) 74 | d['LAT_DEN_COEFF'] = parse_coeff(d, "LAT_DEN_COEFF" , range(1, 21)) 75 | 76 | return d 77 | 78 | 79 | 80 | def read_rpc_xml(rpc_content): 81 | """ 82 | Read RPC file assuming the XML format and determine whether it's a pleiades, spot-6 or worldview image 83 | 84 | Args: 85 | rpc_content: content of RPC sidecar file path read as a string (XML format) 86 | 87 | Returns: 88 | dictionary read from the RPC file 89 | 90 | Raises: 91 | NotImplementedError: if the file format is not handled (the expected keys are not found) 92 | 93 | """ 94 | 95 | # parse the xml file content 96 | tree = ElementTree.fromstring(rpc_content) 97 | 98 | # determine wether it's a pleiades, spot-6 or worldview image 99 | a = tree.find('Metadata_Identification/METADATA_PROFILE') # PHR_SENSOR 100 | b = tree.find('IMD/IMAGE/SATID') # WorldView 101 | parsed_rpc = None 102 | if a is not None: 103 | if a.text in ['PHR_SENSOR', 'S6_SENSOR', 'S7_SENSOR']: 104 | parsed_rpc = read_rpc_xml_pleiades(tree) 105 | elif a.text in ['PNEO_SENSOR']: 106 | parsed_rpc = read_rpc_xml_pleiades_neo(tree) 107 | elif b is not None: 108 | if b.text == 'WV02' or b.text == 'WV01' or b.text == 'WV03': 109 | parsed_rpc = read_rpc_xml_worldview(tree) 110 | 111 | if not parsed_rpc: 112 | raise NotImplementedError() 113 | 114 | return parsed_rpc 115 | 116 | 117 | def read_rpc_xml_pleiades(tree): 118 | """ 119 | Read RPC fields from a parsed XML tree assuming the pleiades, spot-6 XML format 120 | Also reads the inverse model parameters 121 | 122 | Args: 123 | tree: parsed XML tree 124 | 125 | Returns: 126 | dictionary read from the RPC file, or empty dict in case of failure 127 | 128 | """ 129 | m = {} 130 | 131 | def parse_coeff(element, prefix, indices): 132 | """ helper function""" 133 | return ' '.join([element.find("%s_%s" % (prefix, str(x))).text for x in indices]) 134 | 135 | # direct model (LOCALIZATION) 136 | d = tree.find('Rational_Function_Model/Global_RFM/Direct_Model') 137 | m['LON_NUM_COEFF'] = parse_coeff(d, "SAMP_NUM_COEFF", range(1, 21)) 138 | m['LON_DEN_COEFF'] = parse_coeff(d, "SAMP_DEN_COEFF", range(1, 21)) 139 | m['LAT_NUM_COEFF'] = parse_coeff(d, "LINE_NUM_COEFF", range(1, 21)) 140 | m['LAT_DEN_COEFF'] = parse_coeff(d, "LINE_DEN_COEFF", range(1, 21)) 141 | #m['ERR_BIAS'] = parse_coeff(d, "ERR_BIAS", ['X', 'Y']) 142 | 143 | 144 | ## inverse model (PROJECTION) 145 | i = tree.find('Rational_Function_Model/Global_RFM/Inverse_Model') 146 | m['SAMP_NUM_COEFF'] = parse_coeff(i, "SAMP_NUM_COEFF", range(1, 21)) 147 | m['SAMP_DEN_COEFF'] = parse_coeff(i, "SAMP_DEN_COEFF", range(1, 21)) 148 | m['LINE_NUM_COEFF'] = parse_coeff(i, "LINE_NUM_COEFF", range(1, 21)) 149 | m['LINE_DEN_COEFF'] = parse_coeff(i, "LINE_DEN_COEFF", range(1, 21)) 150 | m['ERR_BIAS'] = parse_coeff(i, "ERR_BIAS", ['ROW', 'COL']) 151 | 152 | # validity domains 153 | v = tree.find('Rational_Function_Model/Global_RFM/RFM_Validity') 154 | #vd = v.find('Direct_Model_Validity_Domain') 155 | #m.firstRow = float(vd.find('FIRST_ROW').text) 156 | #m.firstCol = float(vd.find('FIRST_COL').text) 157 | #m.lastRow = float(vd.find('LAST_ROW').text) 158 | #m.lastCol = float(vd.find('LAST_COL').text) 159 | 160 | #vi = v.find('Inverse_Model_Validity_Domain') 161 | #m.firstLon = float(vi.find('FIRST_LON').text) 162 | #m.firstLat = float(vi.find('FIRST_LAT').text) 163 | #m.lastLon = float(vi.find('LAST_LON').text) 164 | #m.lastLat = float(vi.find('LAST_LAT').text) 165 | 166 | # scale and offset 167 | # the -1 in line and column offsets is due to Pleiades RPC convention 168 | # that states that the top-left pixel of an image has coordinates 169 | # (1, 1) 170 | m['LINE_OFF' ] = float(v.find('LINE_OFF').text) - 1 171 | m['SAMP_OFF' ] = float(v.find('SAMP_OFF').text) - 1 172 | m['LAT_OFF' ] = float(v.find('LAT_OFF').text) 173 | m['LONG_OFF' ] = float(v.find('LONG_OFF').text) 174 | m['HEIGHT_OFF' ] = float(v.find('HEIGHT_OFF').text) 175 | m['LINE_SCALE' ] = float(v.find('LINE_SCALE').text) 176 | m['SAMP_SCALE' ] = float(v.find('SAMP_SCALE').text) 177 | m['LAT_SCALE' ] = float(v.find('LAT_SCALE').text) 178 | m['LONG_SCALE' ] = float(v.find('LONG_SCALE').text) 179 | m['HEIGHT_SCALE'] = float(v.find('HEIGHT_SCALE').text) 180 | 181 | return m 182 | 183 | 184 | def read_rpc_xml_pleiades_neo(tree): 185 | """ 186 | Read RPC fields from a parsed XML tree assuming the pleiades NEO XML format 187 | Also reads the inverse model parameters 188 | Args: 189 | tree: parsed XML tree 190 | Returns: 191 | dictionary read from the RPC file, or empty dict in case of failure 192 | """ 193 | m = {} 194 | 195 | def parse_coeff(element, prefix, indices): 196 | """ helper function""" 197 | return ' '.join([element.find("%s_%s" % (prefix, str(x))).text for x in indices]) 198 | 199 | # direct model (LOCALIZATION) 200 | d = tree.find('Rational_Function_Model/Global_RFM/ImagetoGround_Values') 201 | m['LON_NUM_COEFF'] = parse_coeff(d, "LON_NUM_COEFF", range(1, 21)) 202 | m['LON_DEN_COEFF'] = parse_coeff(d, "LON_DEN_COEFF", range(1, 21)) 203 | m['LAT_NUM_COEFF'] = parse_coeff(d, "LAT_NUM_COEFF", range(1, 21)) 204 | m['LAT_DEN_COEFF'] = parse_coeff(d, "LAT_DEN_COEFF", range(1, 21)) 205 | #m['ERR_BIAS'] = parse_coeff(d, "ERR_BIAS", ['X', 'Y']) 206 | 207 | 208 | ## inverse model (PROJECTION) 209 | i = tree.find('Rational_Function_Model/Global_RFM/GroundtoImage_Values') 210 | m['SAMP_NUM_COEFF'] = parse_coeff(i, "SAMP_NUM_COEFF", range(1, 21)) 211 | m['SAMP_DEN_COEFF'] = parse_coeff(i, "SAMP_DEN_COEFF", range(1, 21)) 212 | m['LINE_NUM_COEFF'] = parse_coeff(i, "LINE_NUM_COEFF", range(1, 21)) 213 | m['LINE_DEN_COEFF'] = parse_coeff(i, "LINE_DEN_COEFF", range(1, 21)) 214 | m['ERR_BIAS'] = parse_coeff(i, "ERR_BIAS", ['ROW', 'COL']) 215 | 216 | # validity domains 217 | v = tree.find('Rational_Function_Model/Global_RFM/RFM_Validity') 218 | #vd = v.find('Direct_Model_Validity_Domain') 219 | #m.firstRow = float(vd.find('FIRST_ROW').text) 220 | #m.firstCol = float(vd.find('FIRST_COL').text) 221 | #m.lastRow = float(vd.find('LAST_ROW').text) 222 | #m.lastCol = float(vd.find('LAST_COL').text) 223 | 224 | #vi = v.find('Inverse_Model_Validity_Domain') 225 | #m.firstLon = float(vi.find('FIRST_LON').text) 226 | #m.firstLat = float(vi.find('FIRST_LAT').text) 227 | #m.lastLon = float(vi.find('LAST_LON').text) 228 | #m.lastLat = float(vi.find('LAST_LAT').text) 229 | 230 | # scale and offset 231 | m['LINE_OFF' ] = float(v.find('LINE_OFF').text) 232 | m['SAMP_OFF' ] = float(v.find('SAMP_OFF').text) 233 | m['LAT_OFF' ] = float(v.find('LAT_OFF').text) 234 | m['LONG_OFF' ] = float(v.find('LONG_OFF').text) 235 | m['HEIGHT_OFF' ] = float(v.find('HEIGHT_OFF').text) 236 | m['LINE_SCALE' ] = float(v.find('LINE_SCALE').text) 237 | m['SAMP_SCALE' ] = float(v.find('SAMP_SCALE').text) 238 | m['LAT_SCALE' ] = float(v.find('LAT_SCALE').text) 239 | m['LONG_SCALE' ] = float(v.find('LONG_SCALE').text) 240 | m['HEIGHT_SCALE'] = float(v.find('HEIGHT_SCALE').text) 241 | 242 | return m 243 | 244 | 245 | def read_rpc_xml_worldview(tree): 246 | """ 247 | Read RPC fields from a parsed XML tree assuming the worldview XML format 248 | 249 | Args: 250 | tree: parsed XML tree 251 | 252 | Returns: 253 | dictionary read from the RPC file, or empty dict in case of failure 254 | 255 | """ 256 | m = {} 257 | 258 | # inverse model (PROJECTION) 259 | im = tree.find('RPB/IMAGE') 260 | l = im.find('LINENUMCOEFList/LINENUMCOEF') 261 | m['LINE_NUM_COEFF'] = l.text 262 | l = im.find('LINEDENCOEFList/LINEDENCOEF') 263 | m['LINE_DEN_COEFF'] = l.text 264 | l = im.find('SAMPNUMCOEFList/SAMPNUMCOEF') 265 | m['SAMP_NUM_COEFF'] = l.text 266 | l = im.find('SAMPDENCOEFList/SAMPDENCOEF') 267 | m['SAMP_DEN_COEFF'] = l.text 268 | m['ERR_BIAS'] = float(im.find('ERRBIAS').text) 269 | 270 | # scale and offset 271 | m['LINE_OFF' ] = float(im.find('LINEOFFSET').text) 272 | m['SAMP_OFF' ] = float(im.find('SAMPOFFSET').text) 273 | m['LAT_OFF' ] = float(im.find('LATOFFSET').text) 274 | m['LONG_OFF' ] = float(im.find('LONGOFFSET').text) 275 | m['HEIGHT_OFF' ] = float(im.find('HEIGHTOFFSET').text) 276 | 277 | m['LINE_SCALE' ] = float(im.find('LINESCALE').text) 278 | m['SAMP_SCALE' ] = float(im.find('SAMPSCALE').text) 279 | m['LAT_SCALE' ] = float(im.find('LATSCALE').text) 280 | m['LONG_SCALE' ] = float(im.find('LONGSCALE').text) 281 | m['HEIGHT_SCALE'] = float(im.find('HEIGHTSCALE').text) 282 | 283 | 284 | # # image dimensions 285 | # m.lastRow = int(tree.find('IMD/NUMROWS').text) 286 | # m.lastCol = int(tree.find('IMD/NUMCOLUMNS').text) 287 | 288 | return m 289 | -------------------------------------------------------------------------------- /rpcm/rpc_model.py: -------------------------------------------------------------------------------- 1 | """ 2 | RPC model parsers, localization, and projection 3 | Copyright (C) 2015-19, Carlo de Franchis 4 | Copyright (C) 2015-19, Gabriele Facciolo 5 | Copyright (C) 2015-19, Enric Meinhardt 6 | """ 7 | 8 | import numpy as np 9 | import pyproj 10 | import rasterio 11 | 12 | from rpcm import geo 13 | from rpcm.rpc_file_readers import read_rpc_file 14 | 15 | 16 | class MaxLocalizationIterationsError(Exception): 17 | """ 18 | Custom rpcm Exception. 19 | """ 20 | pass 21 | 22 | 23 | def apply_poly(poly, x, y, z): 24 | """ 25 | Evaluates a 3-variables polynom of degree 3 on a triplet of numbers. 26 | 27 | Args: 28 | poly: list of the 20 coefficients of the 3-variate degree 3 polynom, 29 | ordered following the RPC convention. 30 | x, y, z: triplet of floats. They may be numpy arrays of same length. 31 | 32 | Returns: 33 | the value(s) of the polynom on the input point(s). 34 | """ 35 | out = 0 36 | out += poly[0] 37 | out += poly[1]*y + poly[2]*x + poly[3]*z 38 | out += poly[4]*y*x + poly[5]*y*z +poly[6]*x*z 39 | out += poly[7]*y*y + poly[8]*x*x + poly[9]*z*z 40 | out += poly[10]*x*y*z 41 | out += poly[11]*y*y*y 42 | out += poly[12]*y*x*x + poly[13]*y*z*z + poly[14]*y*y*x 43 | out += poly[15]*x*x*x 44 | out += poly[16]*x*z*z + poly[17]*y*y*z + poly[18]*x*x*z 45 | out += poly[19]*z*z*z 46 | return out 47 | 48 | 49 | def apply_rfm(num, den, x, y, z): 50 | """ 51 | Evaluates a Rational Function Model (rfm), on a triplet of numbers. 52 | 53 | Args: 54 | num: list of the 20 coefficients of the numerator 55 | den: list of the 20 coefficients of the denominator 56 | All these coefficients are ordered following the RPC convention. 57 | x, y, z: triplet of floats. They may be numpy arrays of same length. 58 | 59 | Returns: 60 | the value(s) of the rfm on the input point(s). 61 | """ 62 | return apply_poly(num, x, y, z) / apply_poly(den, x, y, z) 63 | 64 | 65 | def rpc_from_geotiff(geotiff_path): 66 | """ 67 | Read the RPC coefficients from a GeoTIFF file and return an RPCModel object. 68 | 69 | Args: 70 | geotiff_path (str): path or url to a GeoTIFF file 71 | 72 | Returns: 73 | instance of the rpc_model.RPCModel class 74 | """ 75 | with rasterio.open(geotiff_path, 'r') as src: 76 | rpc_dict = src.tags(ns='RPC') 77 | return RPCModel(rpc_dict) 78 | 79 | 80 | def rpc_from_rpc_file(rpc_file_path): 81 | """ 82 | Read the RPC coefficients from a sidecar XML or TXT file and return an RPCModel object. 83 | 84 | Args: 85 | rpc_file_path (str): path to an XML or TXT RPC file 86 | 87 | Returns: 88 | instance of the rpc_model.RPCModel class 89 | """ 90 | return RPCModel(read_rpc_file(rpc_file_path)) 91 | 92 | 93 | class RPCModel: 94 | def __init__(self, d, dict_format="geotiff"): 95 | """ 96 | Args: 97 | d (dict): dictionary read from a geotiff file with 98 | rasterio.open('/path/to/file.tiff', 'r').tags(ns='RPC'), 99 | or from the .__dict__ of an RPCModel object. 100 | dict_format (str): format of the dictionary passed in `d`. 101 | Either "geotiff" if read from the tags of a geotiff file, 102 | or "rpcm" if read from the .__dict__ of an RPCModel object. 103 | """ 104 | if dict_format == "geotiff": 105 | self.row_offset = float(d['LINE_OFF']) 106 | self.col_offset = float(d['SAMP_OFF']) 107 | self.lat_offset = float(d['LAT_OFF']) 108 | self.lon_offset = float(d['LONG_OFF']) 109 | self.alt_offset = float(d['HEIGHT_OFF']) 110 | 111 | self.row_scale = float(d['LINE_SCALE']) 112 | self.col_scale = float(d['SAMP_SCALE']) 113 | self.lat_scale = float(d['LAT_SCALE']) 114 | self.lon_scale = float(d['LONG_SCALE']) 115 | self.alt_scale = float(d['HEIGHT_SCALE']) 116 | 117 | self.row_num = list(map(float, d['LINE_NUM_COEFF'].split())) 118 | self.row_den = list(map(float, d['LINE_DEN_COEFF'].split())) 119 | self.col_num = list(map(float, d['SAMP_NUM_COEFF'].split())) 120 | self.col_den = list(map(float, d['SAMP_DEN_COEFF'].split())) 121 | 122 | if 'LON_NUM_COEFF' in d: 123 | self.lon_num = list(map(float, d['LON_NUM_COEFF'].split())) 124 | self.lon_den = list(map(float, d['LON_DEN_COEFF'].split())) 125 | self.lat_num = list(map(float, d['LAT_NUM_COEFF'].split())) 126 | self.lat_den = list(map(float, d['LAT_DEN_COEFF'].split())) 127 | 128 | elif dict_format == "rpcm": 129 | self.__dict__ = d 130 | 131 | else: 132 | raise ValueError( 133 | "dict_format '{}' not supported. " 134 | "Should be {{'geotiff','rpcm'}}".format(dict_format) 135 | ) 136 | 137 | 138 | def projection(self, lon, lat, alt): 139 | """ 140 | Convert geographic coordinates of 3D points into image coordinates. 141 | 142 | Args: 143 | lon (float or list): longitude(s) of the input 3D point(s) 144 | lat (float or list): latitude(s) of the input 3D point(s) 145 | alt (float or list): altitude(s) of the input 3D point(s) 146 | 147 | Returns: 148 | float or list: horizontal image coordinate(s) (column index, ie x) 149 | float or list: vertical image coordinate(s) (row index, ie y) 150 | """ 151 | nlon = (np.asarray(lon) - self.lon_offset) / self.lon_scale 152 | nlat = (np.asarray(lat) - self.lat_offset) / self.lat_scale 153 | nalt = (np.asarray(alt) - self.alt_offset) / self.alt_scale 154 | 155 | col = apply_rfm(self.col_num, self.col_den, nlat, nlon, nalt) 156 | row = apply_rfm(self.row_num, self.row_den, nlat, nlon, nalt) 157 | 158 | col = col * self.col_scale + self.col_offset 159 | row = row * self.row_scale + self.row_offset 160 | 161 | return col, row 162 | 163 | 164 | def localization(self, col, row, alt, return_normalized=False): 165 | """ 166 | Convert image coordinates plus altitude into geographic coordinates. 167 | 168 | Args: 169 | col (float or list): x image coordinate(s) of the input point(s) 170 | row (float or list): y image coordinate(s) of the input point(s) 171 | alt (float or list): altitude(s) of the input point(s) 172 | 173 | Returns: 174 | float or list: longitude(s) 175 | float or list: latitude(s) 176 | """ 177 | ncol = (np.asarray(col) - self.col_offset) / self.col_scale 178 | nrow = (np.asarray(row) - self.row_offset) / self.row_scale 179 | nalt = (np.asarray(alt) - self.alt_offset) / self.alt_scale 180 | 181 | if not hasattr(self, 'lat_num'): 182 | lon, lat = self.localization_iterative(ncol, nrow, nalt) 183 | else: 184 | lon = apply_rfm(self.lon_num, self.lon_den, nrow, ncol, nalt) 185 | lat = apply_rfm(self.lat_num, self.lat_den, nrow, ncol, nalt) 186 | 187 | if not return_normalized: 188 | lon = lon * self.lon_scale + self.lon_offset 189 | lat = lat * self.lat_scale + self.lat_offset 190 | 191 | return lon, lat 192 | 193 | 194 | def localization_iterative(self, col, row, alt): 195 | """ 196 | Iterative estimation of the localization function (image to ground), 197 | for a list of image points expressed in image coordinates. 198 | 199 | Args: 200 | col, row: normalized image coordinates (between -1 and 1) 201 | alt: normalized altitude (between -1 and 1) of the corresponding 3D 202 | point 203 | 204 | Returns: 205 | lon, lat: normalized longitude and latitude 206 | 207 | Raises: 208 | MaxLocalizationIterationsError: if the while loop exceeds the max 209 | number of iterations, which is set to 100. 210 | """ 211 | # target point: Xf (f for final) 212 | Xf = np.vstack([col, row]).T 213 | 214 | # use 3 corners of the lon, lat domain and project them into the image 215 | # to get the first estimation of (lon, lat) 216 | # EPS is 2 for the first iteration, then 0.1. 217 | lon = -col ** 0 # vector of ones 218 | lat = -col ** 0 219 | EPS = 2 220 | x0 = apply_rfm(self.col_num, self.col_den, lat, lon, alt) 221 | y0 = apply_rfm(self.row_num, self.row_den, lat, lon, alt) 222 | x1 = apply_rfm(self.col_num, self.col_den, lat, lon + EPS, alt) 223 | y1 = apply_rfm(self.row_num, self.row_den, lat, lon + EPS, alt) 224 | x2 = apply_rfm(self.col_num, self.col_den, lat + EPS, lon, alt) 225 | y2 = apply_rfm(self.row_num, self.row_den, lat + EPS, lon, alt) 226 | 227 | n = 0 228 | while not np.all((x0 - col) ** 2 + (y0 - row) ** 2 < 1e-18): 229 | 230 | if n > 100: 231 | raise MaxLocalizationIterationsError("Max localization iterations (100) exceeded") 232 | 233 | X0 = np.vstack([x0, y0]).T 234 | X1 = np.vstack([x1, y1]).T 235 | X2 = np.vstack([x2, y2]).T 236 | e1 = X1 - X0 237 | e2 = X2 - X0 238 | u = Xf - X0 239 | 240 | # project u on the base (e1, e2): u = a1*e1 + a2*e2 241 | # the exact computation is given by: 242 | # M = np.vstack((e1, e2)).T 243 | # a = np.dot(np.linalg.inv(M), u) 244 | # but I don't know how to vectorize this. 245 | # Assuming that e1 and e2 are orthogonal, a1 is given by 246 | # / 247 | num = np.sum(np.multiply(u, e1), axis=1) 248 | den = np.sum(np.multiply(e1, e1), axis=1) 249 | a1 = np.divide(num, den).squeeze() 250 | 251 | num = np.sum(np.multiply(u, e2), axis=1) 252 | den = np.sum(np.multiply(e2, e2), axis=1) 253 | a2 = np.divide(num, den).squeeze() 254 | 255 | # use the coefficients a1, a2 to compute an approximation of the 256 | # point on the gound which in turn will give us the new X0 257 | lon += a1 * EPS 258 | lat += a2 * EPS 259 | 260 | # update X0, X1 and X2 261 | EPS = .1 262 | x0 = apply_rfm(self.col_num, self.col_den, lat, lon, alt) 263 | y0 = apply_rfm(self.row_num, self.row_den, lat, lon, alt) 264 | x1 = apply_rfm(self.col_num, self.col_den, lat, lon + EPS, alt) 265 | y1 = apply_rfm(self.row_num, self.row_den, lat, lon + EPS, alt) 266 | x2 = apply_rfm(self.col_num, self.col_den, lat + EPS, lon, alt) 267 | y2 = apply_rfm(self.row_num, self.row_den, lat + EPS, lon, alt) 268 | 269 | n += 1 270 | 271 | return lon, lat 272 | 273 | 274 | def incidence_angles(self, lon, lat, z): 275 | """ 276 | Compute the local incidence angles (zenith and azimuth). 277 | 278 | Args: 279 | self (rpc_model.RPCModel): camera model 280 | lon, lat, z (floats): longitude, latitude and altitude 281 | 282 | Return: 283 | zenith (float in [0, 90]): angle wrt the vertical, in degrees 284 | azimuth (float in [0, 360]): angle wrt to the north, clockwise, in degrees 285 | """ 286 | # project the input 3D point in the image 287 | col, row = self.projection(lon, lat, z) 288 | 289 | # localize it with two different altitudes 290 | s = 100 # scale factor, in meters 291 | lon0, lat0 = self.localization(col, row, z + 0*s) 292 | lon1, lat1 = self.localization(col, row, z + 1*s) 293 | 294 | # convert to UTM 295 | epsg = geo.compute_epsg(lon, lat) 296 | transformer = pyproj.Transformer.from_crs(4326, epsg, always_xy=True) 297 | [x0, x1], [y0, y1] = transformer.transform([lon0, lon1], [lat0, lat1]) 298 | 299 | # compute local satellite incidence direction 300 | p0 = np.array([x0, y0, z + 0*s]) 301 | p1 = np.array([x1, y1, z + 1*s]) 302 | satellite_direction = (p1 - p0) / np.linalg.norm(p1 - p0) 303 | 304 | # zenith is the angle between the satellite direction and the vertical 305 | zenith = np.degrees(np.arccos(np.dot(satellite_direction, [0, 0, 1]))) 306 | 307 | # azimuth is the clockwise angle with respect to the North 308 | # of the projection of the satellite direction on the horizontal plane 309 | # This can be computed by taking the argument of a complex number 310 | # in a coordinate system where northing is the x axis and easting the y axis 311 | easting, northing = satellite_direction[:2] 312 | azimuth = np.degrees(np.angle(complex(northing, easting))) 313 | 314 | return zenith, azimuth 315 | 316 | 317 | def __repr__(self): 318 | return """ 319 | # Projection function coefficients 320 | col_num = {} 321 | col_den = {} 322 | row_num = {} 323 | row_den = {} 324 | 325 | # Offsets and Scales 326 | row_offset = {} 327 | col_offset = {} 328 | lat_offset = {} 329 | lon_offset = {} 330 | alt_offset = {} 331 | row_scale = {} 332 | col_scale = {} 333 | lat_scale = {} 334 | lon_scale = {} 335 | alt_scale = {}""".format(' '.join(['{: .4f}'.format(x) for x in self.col_num]), 336 | ' '.join(['{: .4f}'.format(x) for x in self.col_den]), 337 | ' '.join(['{: .4f}'.format(x) for x in self.row_num]), 338 | ' '.join(['{: .4f}'.format(x) for x in self.row_den]), 339 | self.row_offset, 340 | self.col_offset, 341 | self.lat_offset, 342 | self.lon_offset, 343 | self.alt_offset, 344 | self.row_scale, 345 | self.col_scale, 346 | self.lat_scale, 347 | self.lon_scale, 348 | self.alt_scale) 349 | 350 | 351 | def write_to_file(self, path): 352 | """ 353 | Write RPC coefficients to a txt file in IKONOS txt format. 354 | 355 | Args: 356 | path (str): path to the output txt file 357 | """ 358 | with open(path, 'w') as f: 359 | 360 | # scale and offset 361 | f.write('LINE_OFF: {:.12f} pixels\n'.format(self.row_offset)) 362 | f.write('SAMP_OFF: {:.12f} pixels\n'.format(self.col_offset)) 363 | f.write('LAT_OFF: {:.12f} degrees\n'.format(self.lat_offset)) 364 | f.write('LONG_OFF: {:.12f} degrees\n'.format(self.lon_offset)) 365 | f.write('HEIGHT_OFF: {:.12f} meters\n'.format(self.alt_offset)) 366 | f.write('LINE_SCALE: {:.12f} pixels\n'.format(self.row_scale)) 367 | f.write('SAMP_SCALE: {:.12f} pixels\n'.format(self.col_scale)) 368 | f.write('LAT_SCALE: {:.12f} degrees\n'.format(self.lat_scale)) 369 | f.write('LONG_SCALE: {:.12f} degrees\n'.format(self.lon_scale)) 370 | f.write('HEIGHT_SCALE: {:.12f} meters\n'.format(self.alt_scale)) 371 | 372 | # projection function coefficients 373 | for i in range(20): 374 | f.write('LINE_NUM_COEFF_{:d}: {:.12f}\n'.format(i+1, self.row_num[i])) 375 | for i in range(20): 376 | f.write('LINE_DEN_COEFF_{:d}: {:.12f}\n'.format(i+1, self.row_den[i])) 377 | for i in range(20): 378 | f.write('SAMP_NUM_COEFF_{:d}: {:.12f}\n'.format(i+1, self.col_num[i])) 379 | for i in range(20): 380 | f.write('SAMP_DEN_COEFF_{:d}: {:.12f}\n'.format(i+1, self.col_den[i])) 381 | 382 | 383 | def to_geotiff_dict(self): 384 | """ 385 | Return a dictionary storing the RPC coefficients as GeoTIFF tags. 386 | 387 | This dictionary d can be written in a GeoTIFF file header with: 388 | 389 | with rasterio.open("/path/to/image.tiff", "r+") as f: 390 | f.update_tags(ns="RPC", **d) 391 | """ 392 | d = {} 393 | d["LINE_OFF"] = self.row_offset 394 | d["SAMP_OFF"] = self.col_offset 395 | d["LAT_OFF"] = self.lat_offset 396 | d["LONG_OFF"] = self.lon_offset 397 | d["HEIGHT_OFF"] = self.alt_offset 398 | 399 | d["LINE_SCALE"] = self.row_scale 400 | d["SAMP_SCALE"] = self.col_scale 401 | d["LAT_SCALE"] = self.lat_scale 402 | d["LONG_SCALE"] = self.lon_scale 403 | d["HEIGHT_SCALE"] = self.alt_scale 404 | 405 | d["LINE_NUM_COEFF"] = " ".join([str(x) for x in self.row_num]) 406 | d["LINE_DEN_COEFF"] = " ".join([str(x) for x in self.row_den]) 407 | d["SAMP_NUM_COEFF"] = " ".join([str(x) for x in self.col_num]) 408 | d["SAMP_DEN_COEFF"] = " ".join([str(x) for x in self.col_den]) 409 | 410 | return {k: d[k] for k in sorted(d)} 411 | 412 | 413 | def equal_offsets(self, other): 414 | """ 415 | Return True if offset coefficients between 416 | two RPCModel instances are equal 417 | """ 418 | return ( 419 | np.allclose(self.row_offset, other.row_offset) 420 | and np.allclose(self.col_offset, other.col_offset) 421 | and np.allclose(self.lat_offset, other.lat_offset) 422 | and np.allclose(self.lon_offset, other.lon_offset) 423 | and np.allclose(self.alt_offset, other.alt_offset) 424 | ) 425 | 426 | def equal_scales(self, other): 427 | """ 428 | Return True if scale coefficients between 429 | two RPCModel instances are equal 430 | """ 431 | return ( 432 | np.allclose(self.row_scale, other.row_scale) 433 | and np.allclose(self.col_scale, other.col_scale) 434 | and np.allclose(self.lat_scale, other.lat_scale) 435 | and np.allclose(self.lon_scale, other.lon_scale) 436 | and np.allclose(self.alt_scale, other.alt_scale) 437 | ) 438 | 439 | def equal_projection(self, other): 440 | """ 441 | Return True if numerator and denominator coefficients of the projection 442 | functions of two RPCModel instances are equal 443 | """ 444 | return ( 445 | np.allclose(self.row_num, other.row_num) 446 | and np.allclose(self.row_den, other.row_den) 447 | and np.allclose(self.col_num, other.col_num) 448 | and np.allclose(self.col_den, other.col_den) 449 | ) 450 | 451 | def equal_localization(self, other): 452 | """ 453 | Return True if numerator and denominator coefficients of the localization 454 | functions of two RPCModel instances are equal 455 | """ 456 | if hasattr(self, "lat_num"): 457 | equal_localization = ( 458 | np.allclose(self.lon_num, other.lon_num) 459 | and np.allclose(self.lon_den, other.lon_den) 460 | and np.allclose(self.lat_num, other.lat_num) 461 | and np.allclose(self.lat_den, other.lat_den) 462 | ) 463 | else: 464 | equal_localization = True 465 | return equal_localization 466 | 467 | def __eq__(self, other): 468 | """ 469 | Compare the coefficients of two RPCModel instances 470 | If all coefficients are equal, the two instances 471 | are considered equal 472 | """ 473 | return ( 474 | self.equal_offsets(other) 475 | and self.equal_scales(other) 476 | and self.equal_projection(other) 477 | and self.equal_localization(other) 478 | ) 479 | -------------------------------------------------------------------------------- /rpcm/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | 4 | import numpy as np 5 | import rasterio 6 | 7 | from rpcm import rpc_model 8 | 9 | 10 | warnings.filterwarnings("ignore", 11 | category=rasterio.errors.NotGeoreferencedWarning) 12 | 13 | 14 | def viewing_direction(zenith, azimut): 15 | """ 16 | Compute the unit 3D vector defined by zenith and azimut angles. 17 | 18 | Args: 19 | zenith (float): angle wrt the vertical direction, in degrees 20 | azimut (float): angle wrt the north direction, in degrees 21 | 22 | Return: 23 | 3-tuple: 3D unit vector giving the corresponding direction 24 | """ 25 | z = np.radians(zenith) 26 | a = np.radians(azimut) 27 | return np.sin(a)*np.sin(z), np.cos(a)*np.sin(z), np.cos(z) 28 | 29 | 30 | def bounding_box2D(pts): 31 | """ 32 | Rectangular bounding box for a list of 2D points. 33 | 34 | Args: 35 | pts (list): list of 2D points represented as 2-tuples or lists of length 2 36 | 37 | Returns: 38 | x, y, w, h (floats): coordinates of the top-left corner, width and 39 | height of the bounding box 40 | """ 41 | dim = len(pts[0]) # should be 2 42 | bb_min = [min([t[i] for t in pts]) for i in range(dim)] 43 | bb_max = [max([t[i] for t in pts]) for i in range(dim)] 44 | return bb_min[0], bb_min[1], bb_max[0] - bb_min[0], bb_max[1] - bb_min[1] 45 | 46 | 47 | def points_apply_homography(H, pts): 48 | """ 49 | Applies an homography to a list of 2D points. 50 | 51 | Args: 52 | H (np.array): 3x3 homography matrix 53 | pts (list): list of 2D points, each point being a 2-tuple or a list 54 | with its x, y coordinates 55 | 56 | Returns: 57 | numpy array: list of transformed points, one per line 58 | """ 59 | pts = np.asarray(pts) 60 | 61 | # convert the input points to homogeneous coordinates 62 | if len(pts[0]) < 2: 63 | print("""points_apply_homography: ERROR the input must be a numpy array 64 | of 2D points, one point per line""") 65 | return 66 | pts = np.hstack((pts[:, :2], np.ones(len(pts)))) 67 | 68 | # apply the transformation 69 | Hpts = (np.dot(H, pts.T)).T 70 | 71 | # normalize the homogeneous result and trim the extra dimension 72 | Hpts = Hpts * (1.0 / np.tile( Hpts[:, 2], (3, 1)) ).T 73 | return Hpts[:, :2] 74 | 75 | 76 | def bounding_box_of_projected_aoi(rpc, aoi, z=0, homography=None): 77 | """ 78 | Return the x, y, w, h pixel bounding box of a projected AOI. 79 | 80 | Args: 81 | rpc (rpc_model.RPCModel): RPC camera model 82 | aoi (geojson.Polygon): GeoJSON polygon representing the AOI 83 | z (float): altitude of the AOI with respect to the WGS84 ellipsoid 84 | homography (2D array, optional): matrix of shape (3, 3) representing an 85 | homography to be applied to the projected points before computing 86 | their bounding box. 87 | 88 | Return: 89 | x, y (ints): pixel coordinates of the top-left corner of the bounding box 90 | w, h (ints): pixel dimensions of the bounding box 91 | """ 92 | lons, lats = np.asarray(aoi['coordinates']).squeeze().T 93 | x, y = rpc.projection(lons, lats, z) 94 | pts = list(zip(x, y)) 95 | if homography is not None: 96 | pts = points_apply_homography(homography, pts) 97 | return np.round(bounding_box2D(pts)).astype(int) 98 | 99 | 100 | def crop_aoi(geotiff, aoi, z=0): 101 | """ 102 | Crop a geographic AOI in a georeferenced image using its RPC functions. 103 | 104 | Args: 105 | geotiff (string): path or url to the input GeoTIFF image file 106 | aoi (geojson.Polygon): GeoJSON polygon representing the AOI 107 | z (float, optional): base altitude with respect to WGS84 ellipsoid (0 108 | by default) 109 | 110 | Return: 111 | crop (array): numpy array containing the cropped image 112 | x, y (ints): pixel coordinates of the top-left corner of the crop 113 | """ 114 | x, y, w, h = bounding_box_of_projected_aoi(rpc_model.rpc_from_geotiff(geotiff), 115 | aoi, z) 116 | with rasterio.open(geotiff, 'r') as src: 117 | crop = src.read(window=((y, y + h), (x, x + w)), boundless=True).squeeze() 118 | return crop, x, y 119 | 120 | 121 | def rasterio_write(path, array, profile={}, tags={}): 122 | """ 123 | Write a numpy array in a tiff or png file with rasterio. 124 | 125 | Args: 126 | path (str): path to the output tiff/png file 127 | array (numpy array): 2D or 3D array containing the image to write 128 | profile (dict): rasterio profile (ie dictionary of metadata) 129 | tags (dict): dictionary with additional geotiff tags 130 | """ 131 | # read image size and number of bands 132 | try: 133 | nbands, height, width = array.shape 134 | except ValueError: # not enough values to unpack (expected 3, got 2) 135 | nbands = 1 136 | height, width = array.shape 137 | array = np.asarray([array]) 138 | 139 | # define image metadata dict 140 | profile.update(driver=rasterio.driver_from_extension(path), 141 | count=nbands, 142 | width=width, 143 | height=height, 144 | dtype=array.dtype) 145 | 146 | # write to file 147 | with rasterio.open(path, 'w', **profile) as dst: 148 | dst.write(array) 149 | dst.update_tags(**tags) 150 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | # instructions for releasing this code to pypi: 3 | # 4 | # vim rpcm/__about__.py # update version number 5 | # python setup.py sdist bdist_wheel 6 | # python -m twine upload dist/rpcm-XXX.tar.gz 7 | # rm -r build dist rpcm.egg-info 8 | 9 | 10 | import os 11 | from codecs import open 12 | from setuptools import setup 13 | 14 | 15 | here = os.path.abspath(os.path.dirname(__file__)) 16 | 17 | package = "rpcm" 18 | 19 | about = {} 20 | with open(os.path.join(here, package, "__about__.py"), "r", "utf-8") as f: 21 | exec(f.read(), about) 22 | 23 | def readme(): 24 | with open(os.path.join(here, 'README.md'), 'r', 'utf-8') as f: 25 | return f.read() 26 | 27 | requirements = ['numpy', 28 | 'pyproj', 29 | 'geojson', 30 | 'rasterio[s3]>=1.2', 31 | 'srtm4>=1.0.2'] 32 | 33 | extras_require = {'test': ['pytest']} 34 | 35 | setup(name=about["__title__"], 36 | version=about["__version__"], 37 | description=about["__description__"], 38 | long_description=readme(), 39 | long_description_content_type='text/markdown', 40 | url=about["__url__"], 41 | author=about["__author__"], 42 | author_email=about["__author_email__"], 43 | packages=[package], 44 | install_requires=requirements, 45 | extras_require=extras_require, 46 | python_requires=">=3", 47 | entry_points=""" 48 | [console_scripts] 49 | rpcm=rpcm.cli:main 50 | """) 51 | -------------------------------------------------------------------------------- /tests/aoi.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Polygon", 3 | "coordinates": [ 4 | [ 5 | [ 6 | -58.60214710235596, 7 | -34.48851858289794 8 | ], 9 | [ 10 | -58.59811305999755, 11 | -34.48851858289794 12 | ], 13 | [ 14 | -58.59811305999755, 15 | -34.485228872794906 16 | ], 17 | [ 18 | -58.60214710235596, 19 | -34.485228872794906 20 | ], 21 | [ 22 | -58.60214710235596, 23 | -34.48851858289794 24 | ] 25 | ] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/test_localization.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from rpcm import RPCModel 6 | from rpcm.rpc_file_readers import read_rpc_ikonos 7 | from rpcm.rpc_model import MaxLocalizationIterationsError 8 | 9 | here = os.path.abspath(os.path.dirname(__file__)) 10 | files_dir = os.path.join(here, "test_rpc_files") 11 | 12 | 13 | def test_localization_break(): 14 | """ 15 | The localization of the top-left corner of the image with the 16 | Skysat L1A RPC included in the test data folder doesn't converge 17 | with an altitude of 70m, but it does with an altitude of 90m, 18 | so we test both behaviors 19 | """ 20 | path = os.path.join(files_dir, "20191015_073816_ssc1d3_0011_basic_l1a_panchromatic_dn_RPC.TXT") 21 | with open(path) as f: 22 | rpc = RPCModel(read_rpc_ikonos(f.read())) 23 | 24 | with pytest.raises(MaxLocalizationIterationsError): 25 | rpc.localization(0, 0, 70) 26 | 27 | rpc.localization(0, 0, 90) 28 | -------------------------------------------------------------------------------- /tests/test_rpc_comparison.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import os 3 | 4 | import pytest 5 | 6 | from rpcm import rpc_from_rpc_file 7 | 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | files_dir = os.path.join(here, "test_rpc_files") 10 | 11 | 12 | @pytest.fixture() 13 | def rpc1(): 14 | """ 15 | Planet L1A RPC fixture 16 | """ 17 | filename = "rpc_PLANET_L1A.txt" 18 | return rpc_from_rpc_file(os.path.join(files_dir, filename)) 19 | 20 | 21 | @pytest.fixture() 22 | def rpc2(): 23 | """ 24 | Planet L1B RPC fixture 25 | """ 26 | filename = "rpc_PLANET_L1B.txt" 27 | return rpc_from_rpc_file(os.path.join(files_dir, filename)) 28 | 29 | 30 | def test_rpc_comparison(rpc1, rpc2): 31 | """ 32 | This test is run on two RPCs coming from Planet Skysat images, 33 | which are equal except for their offset and scale coefficients 34 | (this is because the two images have the same RPC model, 35 | but different sizes) 36 | """ 37 | assert rpc1 == rpc1 38 | assert rpc1 != rpc2 39 | assert not rpc1.equal_offsets(rpc2) 40 | assert not rpc1.equal_scales(rpc2) 41 | assert rpc1.equal_projection(rpc2) 42 | 43 | 44 | @pytest.mark.parametrize("shift, equal", [(1e-8, True), (1e-3, False)]) 45 | def test_rpc_close_comparison(rpc1, shift, equal): 46 | rpc2 = copy.deepcopy(rpc1) 47 | rpc2.col_num = [coeff + shift for coeff in rpc2.col_num] 48 | if equal: 49 | assert rpc2 == rpc1 50 | else: 51 | assert rpc2 != rpc1 52 | -------------------------------------------------------------------------------- /tests/test_rpc_constructors.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from rpcm import RPCModel 4 | from rpcm.rpc_file_readers import read_rpc_file 5 | 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | files_dir = os.path.join(here, "test_rpc_files") 8 | 9 | 10 | def test_rpc_constructors(): 11 | """ 12 | Test that the two constructors of the RPCModel class yield the same object. 13 | """ 14 | filepath = os.path.join(files_dir, "rpc_IKONOS.txt") 15 | 16 | geotiff_dict = read_rpc_file(filepath) 17 | geotiff_rpc = RPCModel(geotiff_dict, dict_format="geotiff") 18 | 19 | rpcm_dict = geotiff_rpc.__dict__ 20 | rpcm_rpc = RPCModel(rpcm_dict, dict_format="rpcm") 21 | 22 | assert geotiff_rpc == rpcm_rpc 23 | -------------------------------------------------------------------------------- /tests/test_rpc_file_parsing.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from rpcm import rpc_from_rpc_file 6 | 7 | here = os.path.abspath(os.path.dirname(__file__)) 8 | files_dir = os.path.join(here, "test_rpc_files") 9 | 10 | 11 | def supported_files(): 12 | """ 13 | Gives the list of files that should be correctly 14 | parsed by `rpc_from_rpc_file` 15 | """ 16 | filenames = [ 17 | "rpc_IKONOS.txt", 18 | "rpc_PLEIADES.xml", 19 | "rpc_SPOT6.xml", 20 | "rpc_WV1.xml", 21 | "rpc_WV2.xml", 22 | "rpc_WV3.xml", 23 | ] 24 | 25 | return [os.path.join(files_dir, filename) for filename in filenames] 26 | 27 | 28 | def unsupported_files(): 29 | """ 30 | Gives the list of files that `rpc_from_rpc_file` should not be 31 | able to parse 32 | """ 33 | filenames = ["rpc_unsupported.xml"] 34 | 35 | return [os.path.join(files_dir, filename) for filename in filenames] 36 | 37 | 38 | @pytest.mark.parametrize("filepath", supported_files()) 39 | def test_successful_rpc_file_parsing(filepath): 40 | """ 41 | Check that filepath can be parsed without errors being raised 42 | """ 43 | rpc_from_rpc_file(filepath) 44 | 45 | 46 | @pytest.mark.parametrize("filepath", unsupported_files()) 47 | def test_failing_rpc_file_parsing(filepath): 48 | """ 49 | Check that filepath raises an error when being parsed 50 | """ 51 | with pytest.raises(NotImplementedError): 52 | rpc_from_rpc_file(filepath) 53 | -------------------------------------------------------------------------------- /tests/test_rpc_files/20191015_073816_ssc1d3_0011_basic_l1a_panchromatic_dn_RPC.TXT: -------------------------------------------------------------------------------- 1 | LINE_OFF: 539.48675 2 | SAMP_OFF: 1293.51565 3 | LAT_OFF: 25.928587267606 4 | LONG_OFF: 49.6688198872119 5 | HEIGHT_OFF: 3287.57296595745 6 | LINE_SCALE: 540.27695 7 | SAMP_SCALE: 1267.13005 8 | LAT_SCALE: 1 9 | LONG_SCALE: 1 10 | HEIGHT_SCALE: 9718.0321 11 | LINE_NUM_COEFF_1: -0.0381273519881014 12 | LINE_NUM_COEFF_2: -5.03793150706338 13 | LINE_NUM_COEFF_3: -224.986041763083 14 | LINE_NUM_COEFF_4: -0.613648823353002 15 | LINE_NUM_COEFF_5: 0.0170613300164835 16 | LINE_NUM_COEFF_6: 0.000123477203888278 17 | LINE_NUM_COEFF_7: -0.000132512634291058 18 | LINE_NUM_COEFF_8: -0.729269982771799 19 | LINE_NUM_COEFF_9: 0.0377692859256352 20 | LINE_NUM_COEFF_10: 0.000934976828790818 21 | LINE_NUM_COEFF_11: -0.000976337364571194 22 | LINE_NUM_COEFF_12: 0.00294969646092627 23 | LINE_NUM_COEFF_13: -0.000433677099575561 24 | LINE_NUM_COEFF_14: -0.00187125564332691 25 | LINE_NUM_COEFF_15: -0.000215418043169574 26 | LINE_NUM_COEFF_16: -0.0024757140241284 27 | LINE_NUM_COEFF_17: 0.000514868847785309 28 | LINE_NUM_COEFF_18: 0.0434198364173715 29 | LINE_NUM_COEFF_19: 7.96321010851711e-06 30 | LINE_NUM_COEFF_20: 2.56748086282788e-05 31 | LINE_DEN_COEFF_1: 1 32 | LINE_DEN_COEFF_2: -0.0254365611546791 33 | LINE_DEN_COEFF_3: 0.00644838322853802 34 | LINE_DEN_COEFF_4: -0.0206450005027071 35 | LINE_DEN_COEFF_5: -0.0988223527444609 36 | LINE_DEN_COEFF_6: 0.000948615911531503 37 | LINE_DEN_COEFF_7: -0.00183843404417772 38 | LINE_DEN_COEFF_8: -0.029355150185236 39 | LINE_DEN_COEFF_9: -0.557768414845639 40 | LINE_DEN_COEFF_10: 1.69918118965579e-05 41 | LINE_DEN_COEFF_11: -0.0103768766869488 42 | LINE_DEN_COEFF_12: 0.0159441878349617 43 | LINE_DEN_COEFF_13: 0.000101647047184534 44 | LINE_DEN_COEFF_14: -0.00982963315554608 45 | LINE_DEN_COEFF_15: -0.00251087176331314 46 | LINE_DEN_COEFF_16: 0.000112351203640688 47 | LINE_DEN_COEFF_17: 0.00181936630549728 48 | LINE_DEN_COEFF_18: 0.246048095089889 49 | LINE_DEN_COEFF_19: -0.000586092298794 50 | LINE_DEN_COEFF_20: 0.000106674293075842 51 | SAMP_NUM_COEFF_1: -0.0208568231289138 52 | SAMP_NUM_COEFF_2: 84.7123952195113 53 | SAMP_NUM_COEFF_3: -1.68781818406203 54 | SAMP_NUM_COEFF_4: -1.86616316961826 55 | SAMP_NUM_COEFF_5: -0.00108497999014548 56 | SAMP_NUM_COEFF_6: -0.000485010496394628 57 | SAMP_NUM_COEFF_7: -0.0174066859088051 58 | SAMP_NUM_COEFF_8: 0.020945017851194 59 | SAMP_NUM_COEFF_9: -0.00403944216931941 60 | SAMP_NUM_COEFF_10: 0.00290664443621184 61 | SAMP_NUM_COEFF_11: 1.67849510390727e-06 62 | SAMP_NUM_COEFF_12: 0.00742644557262118 63 | SAMP_NUM_COEFF_13: 0.000508512856173167 64 | SAMP_NUM_COEFF_14: 8.13091319068204e-05 65 | SAMP_NUM_COEFF_15: -6.50512774862896e-05 66 | SAMP_NUM_COEFF_16: -1.78639038834947e-05 67 | SAMP_NUM_COEFF_17: 0.00178061178365851 68 | SAMP_NUM_COEFF_18: -0.000325900632059863 69 | SAMP_NUM_COEFF_19: -0.00214878782068516 70 | SAMP_NUM_COEFF_20: -1.10117208393379e-05 71 | SAMP_DEN_COEFF_1: 1 72 | SAMP_DEN_COEFF_2: -0.0268838034022625 73 | SAMP_DEN_COEFF_3: 0.0182461864562196 74 | SAMP_DEN_COEFF_4: -0.0206374762907806 75 | SAMP_DEN_COEFF_5: 0.00635885833257241 76 | SAMP_DEN_COEFF_6: 0.0275933007221456 77 | SAMP_DEN_COEFF_7: -0.000780652584798425 78 | SAMP_DEN_COEFF_8: -0.629557542743835 79 | SAMP_DEN_COEFF_9: -0.0472412833907127 80 | SAMP_DEN_COEFF_10: -0.000289841256455273 81 | SAMP_DEN_COEFF_11: 0.000535892786912573 82 | SAMP_DEN_COEFF_12: -0.000508218864391719 83 | SAMP_DEN_COEFF_13: 2.95259433677267e-05 84 | SAMP_DEN_COEFF_14: -0.000733301531591124 85 | SAMP_DEN_COEFF_15: -0.000167084507349422 86 | SAMP_DEN_COEFF_16: 3.51930415572023e-05 87 | SAMP_DEN_COEFF_17: -0.00694394272133181 88 | SAMP_DEN_COEFF_18: -0.00567730287981499 89 | SAMP_DEN_COEFF_19: -0.00175292974053308 90 | SAMP_DEN_COEFF_20: -5.04938965881774e-05 91 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_IKONOS.txt: -------------------------------------------------------------------------------- 1 | LINE_OFF: +005124.00 pixels 2 | SAMP_OFF: +006334.00 pixels 3 | LAT_OFF: -34.90300000 degrees 4 | LONG_OFF: -056.17220000 degrees 5 | HEIGHT_OFF: +0028.000 meters 6 | LINE_SCALE: +005124.00 pixels 7 | SAMP_SCALE: +006334.00 pixels 8 | LAT_SCALE: +00.06610000 degrees 9 | LONG_SCALE: +000.07030000 degrees 10 | HEIGHT_SCALE: +0082.000 meters 11 | LINE_NUM_COEFF_1: -1.490910093701323E-03 12 | LINE_NUM_COEFF_2: +1.221942364020734E+00 13 | LINE_NUM_COEFF_3: -3.210131484158029E-01 14 | LINE_NUM_COEFF_4: +4.148600689146097E-04 15 | LINE_NUM_COEFF_5: -2.314111221699151E-03 16 | LINE_NUM_COEFF_6: +1.973539707175659E-03 17 | LINE_NUM_COEFF_7: -5.191730465725088E-04 18 | LINE_NUM_COEFF_8: +3.988038364824311E-03 19 | LINE_NUM_COEFF_9: +5.757715810052165E-04 20 | LINE_NUM_COEFF_10: +6.805902262216577E-07 21 | LINE_NUM_COEFF_11: -6.018915694466889E-05 22 | LINE_NUM_COEFF_12: +5.506279424857179E-05 23 | LINE_NUM_COEFF_13: +6.034127726059125E-05 24 | LINE_NUM_COEFF_14: -1.098934659399313E-05 25 | LINE_NUM_COEFF_15: -4.593465337244980E-05 26 | LINE_NUM_COEFF_16: -1.405320126256354E-05 27 | LINE_NUM_COEFF_17: +2.862005893796651E-06 28 | LINE_NUM_COEFF_18: -1.708078074208975E-05 29 | LINE_NUM_COEFF_19: +1.739204724519084E-05 30 | LINE_NUM_COEFF_20: -3.792354527256746E-09 31 | LINE_DEN_COEFF_1: +1.000000000000000E+00 32 | LINE_DEN_COEFF_2: +3.196382722606340E-03 33 | LINE_DEN_COEFF_3: -1.855357240722277E-03 34 | LINE_DEN_COEFF_4: +1.482636096915517E-03 35 | LINE_DEN_COEFF_5: -2.824533996865664E-05 36 | LINE_DEN_COEFF_6: -1.452417415042016E-05 37 | LINE_DEN_COEFF_7: -5.401831691937361E-05 38 | LINE_DEN_COEFF_8: +4.976918063279920E-05 39 | LINE_DEN_COEFF_9: +5.027954290420334E-05 40 | LINE_DEN_COEFF_10: -9.198794452911125E-06 41 | LINE_DEN_COEFF_11: +1.640328478928634E-08 42 | LINE_DEN_COEFF_12: +1.423809203801404E-08 43 | LINE_DEN_COEFF_13: +3.220604247529715E-08 44 | LINE_DEN_COEFF_14: +3.577001548130392E-09 45 | LINE_DEN_COEFF_15: -3.963154568167414E-08 46 | LINE_DEN_COEFF_16: -4.204817186058961E-08 47 | LINE_DEN_COEFF_17: +1.564166683596113E-08 48 | LINE_DEN_COEFF_18: -7.895917601135413E-09 49 | LINE_DEN_COEFF_19: +5.353496579371029E-08 50 | LINE_DEN_COEFF_20: +1.929684859424581E-09 51 | SAMP_NUM_COEFF_1: +1.008507647268994E-04 52 | SAMP_NUM_COEFF_2: +2.275388360589146E-01 53 | SAMP_NUM_COEFF_3: +1.128160918719090E+00 54 | SAMP_NUM_COEFF_4: +1.661141551977195E-03 55 | SAMP_NUM_COEFF_5: +3.366264664268842E-03 56 | SAMP_NUM_COEFF_6: +3.711463322728405E-04 57 | SAMP_NUM_COEFF_7: +2.010263011632902E-03 58 | SAMP_NUM_COEFF_8: +3.149849185136837E-04 59 | SAMP_NUM_COEFF_9: -2.184883881198502E-03 60 | SAMP_NUM_COEFF_10: +2.914327355011734E-06 61 | SAMP_NUM_COEFF_11: -2.769104096546294E-05 62 | SAMP_NUM_COEFF_12: +8.940445079857980E-06 63 | SAMP_NUM_COEFF_13: -2.252288518712405E-05 64 | SAMP_NUM_COEFF_14: -2.072005015511335E-06 65 | SAMP_NUM_COEFF_15: +3.788034973072548E-05 66 | SAMP_NUM_COEFF_16: +3.983586660214063E-05 67 | SAMP_NUM_COEFF_17: -9.867814989278225E-06 68 | SAMP_NUM_COEFF_18: -4.516940656979389E-06 69 | SAMP_NUM_COEFF_19: -6.216464860205426E-05 70 | SAMP_NUM_COEFF_20: -1.450545215780897E-08 71 | SAMP_DEN_COEFF_1: +1.000000000000000E+00 72 | SAMP_DEN_COEFF_2: +3.196382722606340E-03 73 | SAMP_DEN_COEFF_3: -1.855357240722277E-03 74 | SAMP_DEN_COEFF_4: +1.482636096915517E-03 75 | SAMP_DEN_COEFF_5: -2.824533996865664E-05 76 | SAMP_DEN_COEFF_6: -1.452417415042016E-05 77 | SAMP_DEN_COEFF_7: -5.401831691937361E-05 78 | SAMP_DEN_COEFF_8: +4.976918063279920E-05 79 | SAMP_DEN_COEFF_9: +5.027954290420334E-05 80 | SAMP_DEN_COEFF_10: -9.198794452911125E-06 81 | SAMP_DEN_COEFF_11: +1.640328478928634E-08 82 | SAMP_DEN_COEFF_12: +1.423809203801404E-08 83 | SAMP_DEN_COEFF_13: +3.220604247529715E-08 84 | SAMP_DEN_COEFF_14: +3.577001548130392E-09 85 | SAMP_DEN_COEFF_15: -3.963154568167414E-08 86 | SAMP_DEN_COEFF_16: -4.204817186058961E-08 87 | SAMP_DEN_COEFF_17: +1.564166683596113E-08 88 | SAMP_DEN_COEFF_18: -7.895917601135413E-09 89 | SAMP_DEN_COEFF_19: +5.353496579371029E-08 90 | SAMP_DEN_COEFF_20: +1.929684859424581E-09 91 | ERR_BIAS: 0003.31 meters 92 | ERR_RAND: 0000.50 meters 93 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_PLANET_L1A.txt: -------------------------------------------------------------------------------- 1 | LINE_OFF: 540.400296515938 2 | SAMP_OFF: 1280.40012503907 3 | LAT_OFF: -32.85 4 | LONG_OFF: 151.7593 5 | HEIGHT_OFF: 31 6 | LINE_SCALE: 540.400296515938 7 | SAMP_SCALE: 1280.40012503907 8 | LAT_SCALE: -0.0234 9 | LONG_SCALE: 0.0357 10 | HEIGHT_SCALE: 2511 11 | LINE_NUM_COEFF_1: 4.1991252591374950 12 | LINE_NUM_COEFF_2: 0.0906856045030080 13 | LINE_NUM_COEFF_3: -2.5406481218661181 14 | LINE_NUM_COEFF_4: 0.0367600016070954 15 | LINE_NUM_COEFF_5: 0.07064333840304031 16 | LINE_NUM_COEFF_6: 0.001341651159452988 17 | LINE_NUM_COEFF_7: -0.1072957566756678 18 | LINE_NUM_COEFF_8: -0.001631962856078851 19 | LINE_NUM_COEFF_9: -1.957821108856383 20 | LINE_NUM_COEFF_10: -0.0003613626175328161 21 | LINE_NUM_COEFF_11: 0.001130672370417538 22 | LINE_NUM_COEFF_12: 1.956207344726211e-06 23 | LINE_NUM_COEFF_13: 0.05664137136033078 24 | LINE_NUM_COEFF_14: -7.99325362870939e-06 25 | LINE_NUM_COEFF_15: -0.001340372301393948 26 | LINE_NUM_COEFF_16: -1.533189884293799 27 | LINE_NUM_COEFF_17: -0.0004101694558260527 28 | LINE_NUM_COEFF_18: 2.154045933716957e-05 29 | LINE_NUM_COEFF_19: -0.08654382380612405 30 | LINE_NUM_COEFF_20: -1.283652065967066e-06 31 | LINE_DEN_COEFF_1: 1 32 | LINE_DEN_COEFF_2: -0.0042183872407591 33 | LINE_DEN_COEFF_3: 0.6165135729884985 34 | LINE_DEN_COEFF_4: 0.002026080041860408 35 | LINE_DEN_COEFF_5: -0.004299962843727709 36 | LINE_DEN_COEFF_6: 6.139262887662811e-05 37 | LINE_DEN_COEFF_7: 0.004168100895604428 38 | LINE_DEN_COEFF_8: -1.877044575643296e-05 39 | LINE_DEN_COEFF_9: 0.2972634846323552 40 | LINE_DEN_COEFF_10: -2.207231108469398e-05 41 | LINE_DEN_COEFF_11: 1.214822424707473e-05 42 | LINE_DEN_COEFF_12: -3.132762202371624e-07 43 | LINE_DEN_COEFF_13: 0.0002328002126772959 44 | LINE_DEN_COEFF_14: 3.727937394358817e-07 45 | LINE_DEN_COEFF_15: -6.286280797165744e-05 46 | LINE_DEN_COEFF_16: 0.0003135042110298291 47 | LINE_DEN_COEFF_17: 3.637491492071228e-05 48 | LINE_DEN_COEFF_18: 2.777360474090565e-06 49 | LINE_DEN_COEFF_19: -0.001541643267666564 50 | LINE_DEN_COEFF_20: -1.109070836767499e-06 51 | SAMP_NUM_COEFF_1: -0.0037169594114830 52 | SAMP_NUM_COEFF_2: -2.7916058688555672 53 | SAMP_NUM_COEFF_3: -0.0315998345568865 54 | SAMP_NUM_COEFF_4: -0.0202496060350615 55 | SAMP_NUM_COEFF_5: 0.0004467962549290577 56 | SAMP_NUM_COEFF_6: -0.001537452375912634 57 | SAMP_NUM_COEFF_7: 0.0001038902381212648 58 | SAMP_NUM_COEFF_8: 0.003303242481855765 59 | SAMP_NUM_COEFF_9: 1.866101423844152e-05 60 | SAMP_NUM_COEFF_10: 0.0005550933642839349 61 | SAMP_NUM_COEFF_11: 3.518680665858609e-05 62 | SAMP_NUM_COEFF_12: -2.489020945813032e-06 63 | SAMP_NUM_COEFF_13: -5.898527552805884e-06 64 | SAMP_NUM_COEFF_14: 3.70765144606274e-05 65 | SAMP_NUM_COEFF_15: -3.50010333476133e-06 66 | SAMP_NUM_COEFF_16: -8.879756298723979e-08 67 | SAMP_NUM_COEFF_17: 1.502370767184827e-06 68 | SAMP_NUM_COEFF_18: 1.553928526538735e-07 69 | SAMP_NUM_COEFF_19: 2.718310715217338e-07 70 | SAMP_NUM_COEFF_20: -1.348094102908501e-06 71 | SAMP_DEN_COEFF_1: 1 72 | SAMP_DEN_COEFF_2: -0.0007064276317558 73 | SAMP_DEN_COEFF_3: -0.0037140845042879 74 | SAMP_DEN_COEFF_4: -0.004630711949997418 75 | SAMP_DEN_COEFF_5: -4.52345229641013e-07 76 | SAMP_DEN_COEFF_6: 6.565316437904263e-06 77 | SAMP_DEN_COEFF_7: -1.392271018351668e-05 78 | SAMP_DEN_COEFF_8: 1.870514464969491e-06 79 | SAMP_DEN_COEFF_9: 3.68471217739052e-06 80 | SAMP_DEN_COEFF_10: 1.106004736915592e-05 81 | SAMP_DEN_COEFF_11: -1.283757867030684e-08 82 | SAMP_DEN_COEFF_12: -9.078954333187424e-09 83 | SAMP_DEN_COEFF_13: 1.934008952157772e-08 84 | SAMP_DEN_COEFF_14: -4.055661624863001e-08 85 | SAMP_DEN_COEFF_15: 1.870151788974248e-08 86 | SAMP_DEN_COEFF_16: -1.847589903296407e-07 87 | SAMP_DEN_COEFF_17: 6.569141681446624e-08 88 | SAMP_DEN_COEFF_18: -6.391906897089226e-09 89 | SAMP_DEN_COEFF_19: -4.49439565992589e-08 90 | SAMP_DEN_COEFF_20: -5.877782791461196e-08 91 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_PLANET_L1B.txt: -------------------------------------------------------------------------------- 1 | LINE_OFF: 675 2 | SAMP_OFF: 1600 3 | LAT_OFF: -32.85 4 | LONG_OFF: 151.7593 5 | HEIGHT_OFF: 31 6 | LINE_SCALE: 675 7 | SAMP_SCALE: 1600 8 | LAT_SCALE: -0.0234 9 | LONG_SCALE: 0.0357 10 | HEIGHT_SCALE: 2511 11 | LINE_NUM_COEFF_1: 4.1991252591374950 12 | LINE_NUM_COEFF_2: 0.0906856045030080 13 | LINE_NUM_COEFF_3: -2.5406481218661181 14 | LINE_NUM_COEFF_4: 0.0367600016070954 15 | LINE_NUM_COEFF_5: 0.07064333840304031 16 | LINE_NUM_COEFF_6: 0.001341651159452988 17 | LINE_NUM_COEFF_7: -0.1072957566756678 18 | LINE_NUM_COEFF_8: -0.001631962856078851 19 | LINE_NUM_COEFF_9: -1.957821108856383 20 | LINE_NUM_COEFF_10: -0.0003613626175328161 21 | LINE_NUM_COEFF_11: 0.001130672370417538 22 | LINE_NUM_COEFF_12: 1.956207344726211e-06 23 | LINE_NUM_COEFF_13: 0.05664137136033078 24 | LINE_NUM_COEFF_14: -7.99325362870939e-06 25 | LINE_NUM_COEFF_15: -0.001340372301393948 26 | LINE_NUM_COEFF_16: -1.533189884293799 27 | LINE_NUM_COEFF_17: -0.0004101694558260527 28 | LINE_NUM_COEFF_18: 2.154045933716957e-05 29 | LINE_NUM_COEFF_19: -0.08654382380612405 30 | LINE_NUM_COEFF_20: -1.283652065967066e-06 31 | LINE_DEN_COEFF_1: 1 32 | LINE_DEN_COEFF_2: -0.0042183872407591 33 | LINE_DEN_COEFF_3: 0.6165135729884985 34 | LINE_DEN_COEFF_4: 0.002026080041860408 35 | LINE_DEN_COEFF_5: -0.004299962843727709 36 | LINE_DEN_COEFF_6: 6.139262887662811e-05 37 | LINE_DEN_COEFF_7: 0.004168100895604428 38 | LINE_DEN_COEFF_8: -1.877044575643296e-05 39 | LINE_DEN_COEFF_9: 0.2972634846323552 40 | LINE_DEN_COEFF_10: -2.207231108469398e-05 41 | LINE_DEN_COEFF_11: 1.214822424707473e-05 42 | LINE_DEN_COEFF_12: -3.132762202371624e-07 43 | LINE_DEN_COEFF_13: 0.0002328002126772959 44 | LINE_DEN_COEFF_14: 3.727937394358817e-07 45 | LINE_DEN_COEFF_15: -6.286280797165744e-05 46 | LINE_DEN_COEFF_16: 0.0003135042110298291 47 | LINE_DEN_COEFF_17: 3.637491492071228e-05 48 | LINE_DEN_COEFF_18: 2.777360474090565e-06 49 | LINE_DEN_COEFF_19: -0.001541643267666564 50 | LINE_DEN_COEFF_20: -1.109070836767499e-06 51 | SAMP_NUM_COEFF_1: -0.0037169594114830 52 | SAMP_NUM_COEFF_2: -2.7916058688555672 53 | SAMP_NUM_COEFF_3: -0.0315998345568865 54 | SAMP_NUM_COEFF_4: -0.0202496060350615 55 | SAMP_NUM_COEFF_5: 0.0004467962549290577 56 | SAMP_NUM_COEFF_6: -0.001537452375912634 57 | SAMP_NUM_COEFF_7: 0.0001038902381212648 58 | SAMP_NUM_COEFF_8: 0.003303242481855765 59 | SAMP_NUM_COEFF_9: 1.866101423844152e-05 60 | SAMP_NUM_COEFF_10: 0.0005550933642839349 61 | SAMP_NUM_COEFF_11: 3.518680665858609e-05 62 | SAMP_NUM_COEFF_12: -2.489020945813032e-06 63 | SAMP_NUM_COEFF_13: -5.898527552805884e-06 64 | SAMP_NUM_COEFF_14: 3.70765144606274e-05 65 | SAMP_NUM_COEFF_15: -3.50010333476133e-06 66 | SAMP_NUM_COEFF_16: -8.879756298723979e-08 67 | SAMP_NUM_COEFF_17: 1.502370767184827e-06 68 | SAMP_NUM_COEFF_18: 1.553928526538735e-07 69 | SAMP_NUM_COEFF_19: 2.718310715217338e-07 70 | SAMP_NUM_COEFF_20: -1.348094102908501e-06 71 | SAMP_DEN_COEFF_1: 1 72 | SAMP_DEN_COEFF_2: -0.0007064276317558 73 | SAMP_DEN_COEFF_3: -0.0037140845042879 74 | SAMP_DEN_COEFF_4: -0.004630711949997418 75 | SAMP_DEN_COEFF_5: -4.52345229641013e-07 76 | SAMP_DEN_COEFF_6: 6.565316437904263e-06 77 | SAMP_DEN_COEFF_7: -1.392271018351668e-05 78 | SAMP_DEN_COEFF_8: 1.870514464969491e-06 79 | SAMP_DEN_COEFF_9: 3.68471217739052e-06 80 | SAMP_DEN_COEFF_10: 1.106004736915592e-05 81 | SAMP_DEN_COEFF_11: -1.283757867030684e-08 82 | SAMP_DEN_COEFF_12: -9.078954333187424e-09 83 | SAMP_DEN_COEFF_13: 1.934008952157772e-08 84 | SAMP_DEN_COEFF_14: -4.055661624863001e-08 85 | SAMP_DEN_COEFF_15: 1.870151788974248e-08 86 | SAMP_DEN_COEFF_16: -1.847589903296407e-07 87 | SAMP_DEN_COEFF_17: 6.569141681446624e-08 88 | SAMP_DEN_COEFF_18: -6.391906897089226e-09 89 | SAMP_DEN_COEFF_19: -4.49439565992589e-08 90 | SAMP_DEN_COEFF_20: -5.877782791461196e-08 91 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_PLEIADES.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DIMAP 5 | PHR_SENSOR 6 | RPC 7 | en 8 | 9 | 10 | 11 | NITF 12 | RPC00B 13 | 14 | 15 | 16 | 0.002345479145616565 17 | 0.9982004051629711 18 | -0.001174804220629302 19 | 0.001005768837041653 20 | 0.0002130698827072838 21 | -6.488133234587516e-05 22 | -2.468963705011824e-05 23 | -0.002187671874412782 24 | 0.0002073269229654572 25 | 5.65587207832036e-08 26 | 6.712368463962803e-08 27 | 1.413942774541883e-05 28 | 7.994781442487608e-06 29 | 3.431829365743842e-06 30 | 4.749825026638551e-05 31 | 0.0003635454067577371 32 | -4.279785629192687e-09 33 | 1.360588633134101e-07 34 | -2.885223383423977e-08 35 | 3.491245378234201e-09 36 | 1 37 | -0.000157198798088042 38 | -0.0002097395199949897 39 | 6.242865860025092e-05 40 | 1.286350207586277e-06 41 | -5.580511927028439e-08 42 | 5.710331978669148e-08 43 | -2.568563110371327e-06 44 | -3.952790958343146e-06 45 | 3.436507892842982e-06 46 | 3.925255551175308e-09 47 | 8.293802794422052e-08 48 | -8.002167621474431e-08 49 | 7.154421148094001e-09 50 | 2.151689283350115e-07 51 | 2.858772052078876e-06 52 | -1.425134664541785e-09 53 | 2.324120248224122e-09 54 | 5.842652352502909e-10 55 | 4.401015857204541e-10 56 | 0.0006648325780254781 57 | 0.02553460478163374 58 | -0.9733015418404606 59 | 0.00158233488686369 60 | 0.0003649886071022664 61 | -1.708296581045822e-06 62 | 4.503581804854702e-06 63 | 0.0004058979896136679 64 | -0.0005102425566691008 65 | -7.437638778031565e-08 66 | -6.64373548913468e-08 67 | -2.520852895449258e-06 68 | -1.082171072435102e-05 69 | -1.869839339439052e-06 70 | 4.802218920815493e-05 71 | 0.000258036783222052 72 | 7.127011817617082e-05 73 | -1.858023745499347e-07 74 | 1.618030519984479e-07 75 | -1.158524099195501e-07 76 | 1 77 | 0.0003280308263392536 78 | 0.0001411867636196782 79 | 3.391552711263321e-06 80 | -5.909890839423872e-06 81 | -4.326157552285826e-08 82 | 1.137191885141747e-07 83 | -4.878926359274893e-05 84 | 0.0001421425121854334 85 | -7.325245708442497e-05 86 | -2.989847793373784e-10 87 | 1.366388424639299e-08 88 | -7.522600734859144e-07 89 | -4.981614358536817e-08 90 | 1.243550435281339e-07 91 | 4.160038774210963e-08 92 | 2.793878969857267e-08 93 | -1.984930844880774e-10 94 | -4.843521985703298e-08 95 | -5.242671540740914e-10 96 | 0.0005113007657218065 97 | 0.0002048011624258839 98 | 99 | 100 | -0.002348990492619796 101 | 1.001823131969253 102 | -0.001209078913893352 103 | -0.001005678460355275 104 | 0.0002179917618000242 105 | 6.032188867264569e-05 106 | -2.473277286685729e-05 107 | 0.002190455615518502 108 | -0.0002202275714937935 109 | 4.561836478756864e-09 110 | -1.193749883440778e-07 111 | -6.622187288151627e-06 112 | -2.438248839672356e-05 113 | -5.563800679699124e-06 114 | 4.997193242924541e-05 115 | 0.0003949073258801377 116 | 6.26156424266283e-09 117 | 2.274363377936989e-07 118 | -1.837833602345209e-06 119 | 5.529858167087769e-09 120 | 1 121 | 0.0001589758501075526 122 | -0.000222117313010845 123 | -6.276491353474424e-05 124 | -1.663085312836401e-06 125 | -9.77217108881141e-08 126 | -4.133265191581069e-08 127 | 2.671493438461805e-06 128 | 1.970219240469247e-05 129 | -5.567365275326929e-06 130 | -1.076997619460509e-08 131 | 6.498381894213351e-08 132 | 5.629336476843006e-07 133 | 1.123888709562682e-08 134 | -2.541731369995831e-07 135 | 4.138751457560986e-07 136 | 2.43831706156568e-09 137 | 1.373835062695638e-09 138 | -1.147147147863098e-10 139 | 6.757130088923075e-10 140 | 0.0006214298792708806 141 | 0.02628088696914566 142 | -1.027459999277219 143 | 0.001599355328408727 144 | -0.0003191339959706392 145 | 6.367749228357248e-07 146 | -2.648907183125757e-06 147 | 0.0004721112079300969 148 | -0.0005221554328288674 149 | 4.458274403982809e-08 150 | -6.811899684464179e-08 151 | 1.471129009267513e-06 152 | -1.742390977092857e-06 153 | 1.979239133154388e-06 154 | -5.098209496877213e-05 155 | -0.0002667877876434404 156 | -7.737906482124386e-05 157 | 8.403067718207476e-08 158 | 7.553682194011114e-07 159 | 1.205427718901844e-07 160 | 1 161 | -0.0003567797330998679 162 | 0.0001104700965815347 163 | -3.748525565953168e-06 164 | 6.903444098822115e-06 165 | 6.451034586273876e-08 166 | 1.060925609464428e-06 167 | 5.004751038921626e-05 168 | -0.0001610256905795282 169 | 7.528107299332276e-05 170 | -4.271780435742103e-09 171 | -2.544796438255415e-08 172 | 4.633124942360306e-07 173 | -5.101621358204604e-08 174 | 4.05916603756919e-07 175 | -1.042866315401422e-06 176 | -3.013343109195307e-08 177 | -8.848234559888026e-10 178 | 4.667388880539766e-08 179 | -5.361223700726976e-10 180 | 0.0007998330971076936 181 | 0.002346812342231713 182 | 183 | 184 | 185 | 1 186 | 1 187 | 36176 188 | 40000 189 | 190 | 191 | -56.28888075471173 192 | -34.95165661791351 193 | -56.050875231979 194 | -34.77387315319408 195 | 196 | 0.1143789948908491 197 | -56.16987799334536 198 | 0.08714875721540594 199 | -34.8627648855538 200 | 80 201 | 70 202 | 19999.5 203 | 20000.5 204 | 18087.5 205 | 18088.5 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_SPOT6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DIMAP 5 | S6_SENSOR 6 | RPC 7 | en 8 | 9 | 10 | 11 | NITF 12 | RPC00B 13 | 14 | 15 | 16 | 1.0 17 | 4.947832145849184E-7 18 | 3.84530220338673E-7 19 | 9.209118072717929E-8 20 | -4.351292035181729E-6 21 | -4.56668195096641E-7 22 | -7.534154630195209E-7 23 | -2.378851685078222E-6 24 | -1.671731563859228E-6 25 | 1.181133261531382E-8 26 | 1.085252708245386E-8 27 | 5.804230540146055E-9 28 | 3.208544401449829E-8 29 | -1.032403055788502E-10 30 | 5.380891957672209E-8 31 | -6.343700255671815E-9 32 | -1.467909800409384E-10 33 | 1.052772251191228E-9 34 | 3.455869338507847E-9 35 | -3.31233238328541E-12 36 | 0.006878777940272462 37 | 0.9899343885638188 38 | -0.0001555122839972616 39 | 0.006249045942233248 40 | -0.003507959387249016 41 | -0.0007883872228062157 42 | -0.0001825345630789948 43 | -0.005789833799791659 44 | -0.0003294496443065662 45 | -4.875940518365806E-7 46 | 2.324186434749039E-6 47 | 6.830055765927333E-5 48 | 4.67062446979694E-5 49 | 7.340416719002223E-8 50 | 0.0001790850048850724 51 | -6.158121533011641E-5 52 | 1.015737752439848E-8 53 | 4.855237115336031E-6 54 | 4.184676100316659E-7 55 | 1.096760551249143E-10 56 | 1.0 57 | 5.770290296966275E-10 58 | 1.381056933764767E-9 59 | 5.24840364826632E-10 60 | -3.312457190436096E-8 61 | -3.298615124704447E-9 62 | -4.883282976824741E-9 63 | -1.957604470211306E-8 64 | -6.354854345656483E-9 65 | -4.386112238895186E-11 66 | -2.507488288791194E-10 67 | -4.358523015135688E-9 68 | 1.063700693689047E-9 69 | -4.674339075739427E-12 70 | -1.401196228737612E-9 71 | 1.709429788745673E-10 72 | 2.070442834515485E-13 73 | -5.945590547423902E-10 74 | -7.790118246208659E-11 75 | 3.15724195878114E-14 76 | 0.0004543034537684302 77 | 0.01813463791574346 78 | -0.9793410080466562 79 | 0.002563749320987634 80 | -0.0003194775402885393 81 | -2.138898076861319E-5 82 | 2.994695906402772E-5 83 | -0.0006496864656641358 84 | -7.255821451095268E-5 85 | -2.254191178077636E-7 86 | 5.068215005933879E-7 87 | 7.651643082102081E-6 88 | 6.824174765381284E-6 89 | 7.266902196919966E-9 90 | 1.016970589842308E-5 91 | 4.427946739965255E-5 92 | -1.029500831318649E-9 93 | 1.005965812373687E-6 94 | 7.941091660184688E-8 95 | 1.928968360191418E-11 96 | 1.99332E-8 97 | 9.00605E-10 98 | 99 | 100 | 1.0 101 | -1.34818889667055E-7 102 | 9.100450898656535E-8 103 | -3.29924546049331E-8 104 | -1.968164014512625E-6 105 | 2.037142890951106E-7 106 | -4.151499022258946E-7 107 | 9.220993594715115E-7 108 | 9.789159974825978E-7 109 | -1.307456811943786E-8 110 | -5.779705716407858E-9 111 | 8.467268927972404E-10 112 | 1.639906522647717E-8 113 | -1.262221338040552E-10 114 | -2.218399974299103E-8 115 | 3.528174862173307E-10 116 | 1.54996143736259E-10 117 | -1.97906628148988E-10 118 | 1.977967316816972E-9 119 | -6.235683135049497E-12 120 | -0.006947241169743709 121 | 1.010089128076644 122 | -0.0001354771748355601 123 | -0.006317220220414627 124 | -0.003668853888863416 125 | 0.0007417688829208666 126 | -0.0001670808025123597 127 | 0.006033214243275441 128 | 0.0003466866886242068 129 | -3.891629160341618E-6 130 | -6.793591843587358E-6 131 | -6.497379637846501E-6 132 | -2.987734485938167E-5 133 | 4.730404851758822E-7 134 | 0.0001287672228750159 135 | -6.745011712610832E-5 136 | -1.092115940828962E-7 137 | 8.734887144737132E-6 138 | 1.223812233988816E-6 139 | -2.617505495427396E-9 140 | 1.0 141 | 3.400726230892766E-8 142 | -6.473532067097489E-9 143 | 7.542547791447065E-10 144 | 1.518627371832105E-8 145 | -1.747168995842767E-11 146 | 3.05841313492323E-10 147 | -9.255463471571919E-10 148 | -5.20512974287028E-8 149 | -8.096206956362942E-12 150 | -1.505208366378682E-11 151 | 8.59027039564985E-11 152 | 1.747651795600173E-8 153 | 4.865035138767011E-13 154 | -9.782901431904161E-10 155 | -8.860511794427533E-10 156 | -4.834405940021392E-13 157 | -7.849918941326027E-13 158 | 3.886187203597985E-10 159 | 1.43701404884432E-14 160 | 0.0003349535349312797 161 | 0.01871325008447279 162 | -1.021099564101154 163 | 0.002500963978141034 164 | 0.0002713997713282419 165 | -7.026974854835017E-8 166 | -3.605698605253001E-5 167 | -0.0005712326311873554 168 | -7.083529791034325E-5 169 | -1.094639098253636E-7 170 | -1.845179316672755E-8 171 | 1.795856339954204E-7 172 | 7.738873643303967E-6 173 | 2.487802194768773E-11 174 | -1.919578931503951E-6 175 | -4.923087233857341E-5 176 | 8.132305517962924E-10 177 | -1.360797544409254E-8 178 | 3.414497541354346E-7 179 | 5.364141030893447E-12 180 | -1.4E-5 181 | 0.000773 182 | 183 | 184 | 185 | 1 186 | 1 187 | 24777 188 | 21953 189 | 190 | 191 | 18.3932164171 192 | -72.4395036093 193 | 18.7570975666 194 | -72.0984267043 195 | 196 | 0.17163418 197 | -72.26895693 198 | 0.18241454 199 | 18.57519833 200 | 500.0 201 | 500.0 202 | 10976.5 203 | 10976.5 204 | 12388.5 205 | 12388.5 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_WV2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23.15 5 | 2015-09-30T12:01:14.000000Z 6 | 15EUSI-2042-01_I079941_ST02-P146295 7 | None 8 | StereoOR2A 9 | RGB 10 | UNB 11 | 20289 12 | 28244 13 | Stereo OR2A 14 | Stereo 15 | 2 16 | Corrected 17 | Off 18 | 16 19 | None 20 | GeoTIFF 21 | 22 | -3.882937500000000e-01 23 | 4.569999975000000e+01 24 | 7.709999999999999e+01 25 | -2.612002500000000e-01 26 | 4.569999975000000e+01 27 | 7.709999999999999e+01 28 | -2.612002500000000e-01 29 | 4.560870375000000e+01 30 | 7.709999999999999e+01 31 | -3.882937500000000e-01 32 | 4.560870375000000e+01 33 | 7.709999999999999e+01 34 | 1.103623000000000e-02 35 | 5.740000000000000e-02 36 | 10 37 | 38 | 39 | -3.882937500000000e-01 40 | 4.569999975000000e+01 41 | 7.709999999999999e+01 42 | -2.612002500000000e-01 43 | 4.569999975000000e+01 44 | 7.709999999999999e+01 45 | -2.612002500000000e-01 46 | 4.560870375000000e+01 47 | 7.709999999999999e+01 48 | -3.882937500000000e-01 49 | 4.560870375000000e+01 50 | 7.709999999999999e+01 51 | 9.713071000000000e-03 52 | 6.300000000000000e-02 53 | 14 54 | 55 | 56 | -3.882937500000000e-01 57 | 4.569999975000000e+01 58 | 7.709999999999999e+01 59 | -2.612002500000000e-01 60 | 4.569999975000000e+01 61 | 7.709999999999999e+01 62 | -2.612002500000000e-01 63 | 4.560870375000000e+01 64 | 7.709999999999999e+01 65 | -3.882937500000000e-01 66 | 4.560870375000000e+01 67 | 7.709999999999999e+01 68 | 1.260825000000000e-02 69 | 5.430000000000000e-02 70 | 14 71 | 72 | 73 | WV02 74 | FullSwath 75 | Forward 76 | 1030050045F8F000 77 | 2015-09-30T10:56:56.973685Z 78 | 5.000000000000000e+03 79 | 2.000000000000000e-04 80 | 7.060000000000000e-01 81 | 7.130000000000000e-01 82 | 7.090000000000000e-01 83 | 5.700000000000000e-01 84 | 5.720000000000000e-01 85 | 5.710000000000000e-01 86 | 6.360000000000000e-01 87 | 6.023000000000000e+01 88 | 8.520000000000000e+00 89 | 1.620000000000000e+02 90 | 1.621000000000000e+02 91 | 1.621000000000000e+02 92 | 4.020000000000000e+01 93 | 4.030000000000000e+01 94 | 4.030000000000000e+01 95 | 1.726000000000000e+02 96 | 1.728000000000000e+02 97 | 1.727000000000000e+02 98 | 5.240000000000000e+01 99 | 5.270000000000000e+01 100 | 5.260000000000000e+01 101 | -3.100000000000000e+01 102 | -3.080000000000000e+01 103 | -3.090000000000000e+01 104 | 1.110000000000000e+01 105 | 1.120000000000000e+01 106 | 1.120000000000000e+01 107 | 3.270000000000000e+01 108 | 3.270000000000000e+01 109 | 3.270000000000000e+01 110 | 4.700000000000000e+00 111 | 0.000000000000000e+00 112 | PS 113 | R 114 | R 115 | 31358 116 | 117 | 118 | 2015-09-30T10:56:57.204388Z 119 | 2015-09-30T10:56:57.204388Z 120 | WE 121 | 6.378137000000000e+06 122 | 2.982572235630000e+02 123 | 124 | 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 125 | 126 | Geographic (Lat/Long) 127 | 17 128 | 129 | 6.366197723675813e+06 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 130 | 131 | DD 132 | -3.882937500000000e-01 133 | 4.569999975000000e+01 134 | 0.000000000000000e+00 135 | 4.500000000000000e-06 136 | 4.500000000000000e-06 137 | 5.000000000000000e-01 138 | -3.882937500000000e-01 139 | 4.569999975000000e+01 140 | 7.709999999999999e+01 141 | -2.612002500000000e-01 142 | 4.569999975000000e+01 143 | 7.709999999999999e+01 144 | -2.612002500000000e-01 145 | 4.560870375000000e+01 146 | 7.709999999999999e+01 147 | -3.882937500000000e-01 148 | 4.560870375000000e+01 149 | 7.709999999999999e+01 150 | Base Elevation 151 | 7.709999999999999e+01 152 | 0 153 | 154 | 155 | 156 | RGB 157 | 1 158 | 28244 159 | 20289 160 | Pixels 161 | 0 162 | 163 | 15SEP30105657-S2AS-15EUSI-2042-01.TIF 164 | 0 165 | 0 166 | 28243 167 | 0 168 | 28243 169 | 20288 170 | 0 171 | 20288 172 | -3.882937500000000e-01 173 | 4.569999975000000e+01 174 | -2.612002500000000e-01 175 | 4.569999975000000e+01 176 | -2.612002500000000e-01 177 | 4.560870375000000e+01 178 | -3.882937500000000e-01 179 | 4.560870375000000e+01 180 | -3.882937500000000e-01 181 | 4.569999975000000e+01 182 | -2.612002500000000e-01 183 | 4.569999975000000e+01 184 | -2.612002500000000e-01 185 | 4.560870375000000e+01 186 | -3.882937500000000e-01 187 | 4.560870375000000e+01 188 | 189 | 190 | 191 | 1 192 | 193 | 15SEP30105549-S2AS-15EUSI-2042-01.TIF 194 | 15SEP30105657-S2AS-15EUSI-2042-01.TIF 195 | 7.400000000000000e-01 196 | 4.571894000000000e+01 197 | -4.373410000000000e-01 198 | 4.558034200000000e+01 199 | -2.122340000000000e-01 200 | 3.300000000000000e+01 201 | 3.291000000000000e+01 202 | 1.827000000000000e+01 203 | 1.788000000000000e+01 204 | 6.667000000000000e+01 205 | 6.713000000000000e+01 206 | 207 | 208 | 209 | WV02 210 | RGB 211 | RPC00B 212 | 213 | 2.668000000000000e+01 214 | 1.400000000000000e-01 215 | 10108 216 | 14104 217 | 4.565430000000000e+01 218 | -3.248000000000000e-01 219 | 97 220 | 10903 221 | 14264 222 | 4.570000000000000e-02 223 | 6.360000000000000e-02 224 | 501 225 | 226 | 1.594159000000000e-03 1.867963000000000e-06 -9.314605000000000e-01 -6.956394000000000e-02 5.116923000000000e-07 2.048082000000000e-05 -2.968378000000000e-04 -1.886473000000000e-06 -7.303114000000000e-04 -1.122072000000000e-05 1.108643000000000e-08 -1.558782000000000e-06 -7.085399999999999e-08 2.072282000000000e-08 -2.911408000000000e-08 -5.568963000000000e-07 -1.233538000000000e-07 4.601875000000000e-07 -1.094849000000000e-06 -7.440788000000000e-08 227 | 228 | 229 | 1.000000000000000e+00 2.185499000000000e-06 7.839770000000000e-04 -1.663701000000000e-04 8.480122000000000e-08 0.000000000000000e+00 -3.141143000000000e-07 3.503175000000000e-08 6.041152000000000e-07 -8.217922000000000e-08 2.661074000000000e-08 -4.011493000000000e-06 0.000000000000000e+00 0.000000000000000e+00 2.424781000000000e-08 2.447341000000000e-08 1.093976000000000e-08 -2.118593000000000e-07 9.135875999999999e-08 -1.706163000000000e-08 230 | 231 | 232 | 1.188955000000000e-05 9.908708000000001e-01 -3.800477000000000e-06 -9.793786000000001e-03 -4.606007000000000e-07 4.003363000000000e-04 -9.586380000000000e-05 -3.190926000000000e-06 0.000000000000000e+00 -1.016823000000000e-05 4.003998000000000e-07 2.325708000000000e-07 3.993205000000000e-08 1.830419000000000e-07 0.000000000000000e+00 0.000000000000000e+00 -5.605098000000000e-08 -1.395735000000000e-07 9.613687000000000e-08 0.000000000000000e+00 233 | 234 | 235 | 1.000000000000000e+00 -4.965446000000000e-06 -4.807880000000000e-07 -3.935717000000000e-04 1.139126000000000e-08 6.974974000000000e-08 -2.717830000000000e-07 -1.211950000000000e-07 3.647002000000000e-08 -7.035523999999999e-08 -2.087007000000000e-08 1.714795000000000e-06 0.000000000000000e+00 -7.949217000000001e-07 -1.422145000000000e-08 0.000000000000000e+00 0.000000000000000e+00 -9.274687000000001e-06 1.222910000000000e-08 0.000000000000000e+00 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /tests/test_rpc_files/rpc_unsupported.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 411 4 | 2525 5 | 25.2882 6 | -0.0314 7 | 1 0.0004071644009999 0.5067559326138362 0.102385222866417 -0.007691276074056833 0.0003069412806250116 0.08974431552012505 -8.370895972150096e-06 0.1983591916507734 0.001948616406989111 0.0003052768869492546 5.476667957573127e-08 0.0005098678758169479 8.766982274961228e-06 5.165800092359645e-05 -0.0004810701809183786 -0.0001188964203070696 2.661082368411328e-06 -0.001531901711967229 -1.679506482617943e-06 8 | 4.2354012397360528 0.2239999854910411 -3.0655659153410237 -1.7568771040957150 0.1485304641880656 0.05184376858970338 -1.312916326694124 -0.0007746491134053799 -1.829959870633601 -0.2192111879195703 0.0444907686853161 -1.762366253617911e-05 0.09842359097717444 0.0006672295254140963 -0.001125402779069747 -1.04690517512365 -0.2135099846886485 0.0001596461580829932 -0.9346619004197527 -0.004517889380670648 9 | 675 10 | 675 11 | 48.1103 12 | 0.0383 13 | 1 -0.0069131543672599 -0.0035958283683015 -0.005768817914771954 4.10883177389562e-05 9.317103764218148e-05 -4.071515629238354e-06 0.0002764292211762797 5.54590501547405e-06 2.900229226643178e-05 8.764394074250148e-08 7.220266183706659e-07 3.285310235331186e-08 -3.868934702896876e-07 -9.106382417962488e-07 -2.066814722359951e-07 -1.029228880538978e-07 -2.08263302337466e-06 -3.053351851447986e-07 -1.578047672843795e-07 14 | -0.0560200226042460 -2.8533850877890363 -0.0236624718342863 -0.1315468139691276 -0.0003875899905343987 0.0005602792637774801 -0.0003113133202855572 0.05097926737736858 3.887138793365077e-05 0.0006383711448739139 1.447162609146783e-05 -0.0009090286145994677 -1.144345965542303e-05 -5.253503059132947e-06 2.172147466546848e-05 3.159827941523795e-07 2.463370890671675e-06 2.19722975442307e-05 2.811735826687232e-07 -3.177260614379577e-06 15 | 1600 16 | 1600 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit on errors. It's far from perfect, but better than nothing: 4 | # http://mywiki.wooledge.org/BashFAQ/105 5 | set -e 6 | 7 | IMG1="http://menthe.ovh.hw.ipol.im/IARPA_data/cloud_optimized_geotif/01SEP15WV031000015SEP01135603-P1BS-500497284040_01_P001_________AAE_0AAAAABPABP0.TIF" 8 | IMG2="http://menthe.ovh.hw.ipol.im/IARPA_data/cloud_optimized_geotif/02APR15WV031000015APR02134716-P1BS-500276959010_02_P001_________AAE_0AAAAABPABB0.TIF" 9 | 10 | rpcm projection ${IMG1} --lon -58.6096 --lat -34.4732 11 | rpcm localization ${IMG1} -x 21377.790 -y 21481.055 -z 100 12 | rpcm crop ${IMG1} tests/aoi.geojson c.tif 13 | rpcm projection ${IMG1} --lon -58.602 --lat -34.488 --crop c.tif 14 | rpcm footprint ${IMG1} 15 | rpcm angle ${IMG1} ${IMG2} 16 | --------------------------------------------------------------------------------