def run_pipeline(self, xx, yy, zz, incrs, region_name): """ MLLW to NAVD88 """ req_hcrs_epsg = 26919 req_vcrs_epsg = 'mllw' out_vcrs_epsg = 5703 # parse the provided CRS cmpd_incrs = CompoundCRS.from_wkt(incrs.to_wkt()) if len(cmpd_incrs.sub_crs_list) == 2: inhcrs, invcrs = cmpd_incrs.sub_crs_list assert inhcrs.to_epsg() == req_hcrs_epsg assert invcrs.to_epsg() == req_vcrs_epsg elif not cmpd_incrs.is_vertical: assert incrs.to_epsg() == req_hcrs_epsg # build the output crs comp_crs = CompoundCRS( name="NAD83 UTM19 + NAVD88", components=[f"EPSG:{req_hcrs_epsg}", f"EPSG:{out_vcrs_epsg}"]) # get the transform at the sparse points transformer = Transformer.from_pipeline(f'proj=pipeline \ step inv proj=utm zone=19 \ step inv proj=vgridshift grids={self.config.grid_files[f"{region_name}/mllw.gtx"]} \ step proj=vgridshift grids={self.config.grid_files[f"{region_name}/tss.gtx"]} \ step proj=utm zone=19') result = transformer.transform(xx=xx, yy=yy, zz=zz) self.config.logger.debug('Applying pipeline: {}'.format(transformer)) return result, comp_crs
def test_compund_crs(): vertcrs = VerticalCRS( name="NAVD88 height", datum="North American Vertical Datum 1988", vertical_cs=VerticalCS(), geoid_model="GEOID12B", ) projcrs = ProjectedCRS( name="NAD83 / Pennsylvania South", conversion=LambertConformalConic2SPConversion( latitude_false_origin=39.3333333333333, longitude_false_origin=-77.75, latitude_first_parallel=40.9666666666667, latitude_second_parallel=39.9333333333333, easting_false_origin=600000, northing_false_origin=0, ), geodetic_crs=GeographicCRS(datum="North American Datum 1983"), cartesian_cs=Cartesian2DCS(), ) compcrs = CompoundCRS( name="NAD83 / Pennsylvania South + NAVD88 height", components=[projcrs, vertcrs] ) assert compcrs.name == "NAD83 / Pennsylvania South + NAVD88 height" assert compcrs.type_name == "Compound CRS" assert compcrs.sub_crs_list[0].type_name == "Projected CRS" assert compcrs.sub_crs_list[1].type_name == "Vertical CRS"
def test_compund_crs__from_methods(): crs = CompoundCRS.from_string("EPSG:4326+5773") with pytest.raises(CRSError, match="Invalid type"): CompoundCRS.from_epsg(4326) assert_maker_inheritance_valid(crs, CompoundCRS) with pytest.raises(CRSError, match="Invalid type"): CompoundCRS.from_proj4("+proj=longlat +datum=WGS84 +vunits=m") assert_maker_inheritance_valid(CompoundCRS.from_user_input(crs), CompoundCRS) assert_maker_inheritance_valid(CompoundCRS.from_json(crs.to_json()), CompoundCRS) assert_maker_inheritance_valid( CompoundCRS.from_json_dict(crs.to_json_dict()), CompoundCRS)
def _update_and_build_compound(self): """ Create a compound crs object attribute and mark the object as valid if there is a horizontal crs, a vertical crs, and if the vertical crs includes the pipeline and regions in the remarks. Returns ------- None. """ if self._vert and self._regions: self._vert, self._vyperdatum_str, self._pipeline_str = build_valid_vert_crs( self._vert, self._regions, self.datum_data) if self._hori and self._vert and self._valid_vert(): compound_name = f'{self._hori.name} + {self._vert.name}' self.ccrs = CompoundCRS(compound_name, [self._hori, self._vert]) self._is_valid = True vert_axis = self._vert.axis_info[0] if vert_axis.direction == 'up': self._is_height = True elif vert_axis.direction == 'down': self._is_height = False else: raise ValueError('no direction defined for the vertical crs.')
class VyperPipelineCRS: """ A container for developing and validating compound crs objects built around pyproj crs objects and the vypercrs.VerticalPipelineCRS object. """ def __init__(self, datum_data: object, new_crs: Union[str, int, tuple] = None, regions: [str] = None): self.datum_data = datum_data self.vdatum_version = datum_data.vdatum_version self._is_valid = False self._ccsr = None self._vert = None self._hori = None self._regions = [] self._vyperdatum_str = None self._pipeline_str = None self._is_height = None if new_crs is not None: self.set_crs(new_crs, regions) def set_crs(self, new_crs: Union[str, int, tuple], regions: [str] = None): """ Set the object vertical and / or horizontal crs using either an epsg code or wkt. Regions are also optionally set with a list of VDatum region strings. Parameters ---------- new_crs : Union[str, int, tuple] A wkt string or a vyperdatum vertical datum definition string or an epsg code may be provided either on its own or as a tuple pair. While the order (horizontal or vertical) does not matter, if they contain information for the same frame (horizontal or vertical) the second will overwrite the first. Once the provided object datums are set, the object compound attribute is set if the horizontal, verical, and region information is available. regions : [str], optional A list of VDatum region strings. These values will overwrite the values contained in the vertical WKT if they exist. The default is None. Raises ------ ValueError If a tuple of length greater than two is provided or if the provided value(s) are not a string or an integer. Returns ------- None. """ # check the input type if type(new_crs) == tuple: if len(new_crs) > 2: len_crs = len(new_crs) raise ValueError( f'The provided crs {new_crs} is {len_crs} in length but should have a length of two.' ) else: new_crs = (new_crs, ) # create pyproj crs based on the input type for entry in new_crs: if type(entry) == str: crs_str = entry if entry.lower() in datum_definition: if entry == 'ellipse' and self._hori: entry = f'{self._hori.name}_ellipse' tmp_crs = VerticalPipelineCRS(datum_data=self.datum_data, vert_datum_name=entry) crs_str = tmp_crs.to_wkt() crs = CRS.from_wkt(crs_str) self._set_single(crs) elif type(entry) == int: crs = CRS.from_epsg(entry) self._set_single(crs) else: raise ValueError( f'The crs description type {entry} is not recognized.') if regions: self._regions = regions self._update_and_build_compound() def update_regions(self, regions: [str]): """ Update the regions object attribute. Parameters ---------- regions : [str] DESCRIPTION. Returns ------- None. """ self._regions = regions self._update_and_build_compound() def _set_single(self, crs: CRS): """ Assign the provided pyproj crs object to the object attribute representing either the vertical crs or the horizontal crs. If the object contains a new vertical crs and contains the remarks for the regions, the regions are also extracted and assigned to the associated object attribute. Parameters ---------- crs : pyproj.crs.CRS The new crs. Returns ------- None. """ new_vert = False if crs_is_compound(crs): self.ccrs = crs self._hori = crs.sub_crs_list[0] self._vert = crs.sub_crs_list[1] new_vert = True elif crs.is_geocentric: raise ValueError( 'Geocentric cooridinate systems are not supported.') elif len(crs.axis_info) > 2: # assuming 3D crs if not compound but axis length is > 2. Break into compound crs. if crs.to_epsg() == NAD83_3D: # if 3d nad83, go to 2d nad83 self._hori = CRS.from_epsg(NAD83_2D) elif crs.to_epsg() == ITRF2008_3D: # 3d wgs84/itrf2008, go to 2d self._hori = CRS.from_epsg(ITRF2008_2D) elif crs.to_epsg() == ITRF2014_3D: # 3d itrf2014, go to 2d self._hori = CRS.from_epsg(ITRF2014_2D) else: raise NotImplementedError( f'A 3D coordinate system was provided that is not yet implemented: {crs.to_epsg()}' ) self._vert = VerticalPipelineCRS( datum_data=self.datum_data, vert_datum_name=f'{self._hori.name}_ellipse').to_crs() new_vert = True elif crs.is_vertical: self._vert = crs new_vert = True else: self._hori = crs if new_vert: # get the regions from the wkt if available tmp_vert = VerticalPipelineCRS(datum_data=self.datum_data) tmp_vert.from_wkt(self._vert.to_wkt()) if len(tmp_vert.regions) > 0: self._regions = tmp_vert.regions # ideally we would pull the vyperdatum name string here too def _update_and_build_compound(self): """ Create a compound crs object attribute and mark the object as valid if there is a horizontal crs, a vertical crs, and if the vertical crs includes the pipeline and regions in the remarks. Returns ------- None. """ if self._vert and self._regions: self._vert, self._vyperdatum_str, self._pipeline_str = build_valid_vert_crs( self._vert, self._regions, self.datum_data) if self._hori and self._vert and self._valid_vert(): compound_name = f'{self._hori.name} + {self._vert.name}' self.ccrs = CompoundCRS(compound_name, [self._hori, self._vert]) self._is_valid = True vert_axis = self._vert.axis_info[0] if vert_axis.direction == 'up': self._is_height = True elif vert_axis.direction == 'down': self._is_height = False else: raise ValueError('no direction defined for the vertical crs.') def _valid_vert(self) -> bool: """ See if there is a vertical crs in the object and if it has regions and and a pipeline in the remarks. Returns ------- bool True if there is a vertical crs object and the remarks include the pipeline and regions. """ valid = False if self._vert and self._vert.remarks: have_region = 'regions' in self._vert.remarks have_pipeline = 'pipeline' in self._vert.remarks have_version = 'vyperdatum' in self._vert.remarks have_datum = 'base_datum' in self._vert.remarks if have_region and have_pipeline and have_version and have_datum: valid = True return valid @property def is_valid(self): return self._is_valid @property def vertical(self): if self._valid_vert(): return self._vert else: return None @property def horizontal(self): return self._hori @property def regions(self): return self._regions @property def vyperdatum_str(self): return self._vyperdatum_str @property def pipeline_string(self): return self._pipeline_str @property def is_height(self): return self._is_height def to_wkt(self): if self._is_valid: return self.ccrs.to_wkt() else: return None