diff --git a/swmmio/core.py b/swmmio/core.py index 2468e52..afd3e8d 100644 --- a/swmmio/core.py +++ b/swmmio/core.py @@ -4,6 +4,7 @@ from time import ctime import os import glob +from pyproj import CRS import pandas as pd import numpy as np @@ -104,13 +105,15 @@ def __init__(self, in_file_path, crs=None, include_rpt=True): ---------- in_file_path : str Path to local INP file or URL to remote INP file - crs : str, optional - String representation of a coordinate reference system, by default None + crs : pyproj.CRS, optional + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. include_rpt : bool, optional whether to include data from an RPT (if an RPT exists), by default True """ - self.crs = None + self._crs = None inp_path = None # if the input is a URL, download it to a temp location @@ -135,7 +138,7 @@ def __init__(self, in_file_path, crs=None, include_rpt=True): self.rpt = None # until we can confirm it initializes properly self.bbox = None # to remember how the model data was clipped self.scenario = '' # self._get_scenario() - self.crs = crs # coordinate reference system + self._crs = CRS.from_user_input(crs) if crs else None # coordinate reference system # try to initialize a companion RPT object rpt_path = os.path.join(wd, name + '.rpt') @@ -258,6 +261,14 @@ def conduits(self): return df + @property + def crs(self): + return self._crs + + @crs.setter + def crs(self, value): + self._crs = CRS.from_user_input(value) if value else None + @property def orifices(self): """ @@ -640,6 +651,7 @@ def __init__(self, file_path): self._inlet_usage_df = None self._patterns_df = None self._controls_df = None + self._symbols_df = None SWMMIOFile.__init__(self, file_path) # run the superclass init @@ -687,6 +699,7 @@ def __init__(self, file_path): '[INLET_USAGE]', '[PATTERNS]', '[CONTROLS]', + '[SYMBOLS]', ] def save(self, target_path=None): @@ -1776,12 +1789,37 @@ def inlet_usage(self): if self._inlet_usage_df is None: self._inlet_usage_df = dataframe_from_inp(self.path, "[INLET_USAGE]") return self._inlet_usage_df - + @inlet_usage.setter def inlet_usage(self, df): """Set inp.inlet_usage DataFrame.""" self._inlet_usage_df = df + @property + def symbols(self): + """ + Get/set symbols section of INP file. + + Section: [SYMBOLS] + Purpose: Assigns X, Y coordinates to rain gage symbols. + Columns: + - Name: name of rain gage. + - X: horizontal coordinate relative to origin in lower left of map. + - Y: vertical coordinate relative to origin in lower left of map + + Returns + ------- + pandas.DataFrame + """ + if self._symbols_df is not None: + return self._symbols_df + self._symbols_df = dataframe_from_inp(self.path, "[SYMBOLS]") + return self._symbols_df + + @symbols.setter + def symbols(self, df): + """Set inp.symbols DataFrame.""" + self._symbols_df = df def drop_invalid_model_elements(inp): """ diff --git a/swmmio/defs/inp_sections.yml b/swmmio/defs/inp_sections.yml index 715c0a7..729e64e 100644 --- a/swmmio/defs/inp_sections.yml +++ b/swmmio/defs/inp_sections.yml @@ -80,6 +80,7 @@ inp_file_objects: INLET_USAGE: [Link, Inlet, Node, Number, "%Clogged", Qmax, aLocal, wLocal, Placement] PATTERNS: [Name, Type, Factors] CONTROLS: [blob] + SYMBOLS: [Name, X, Y] inp_section_tags: ['[TITLE', '[OPTION', '[FILE', '[RAINGAGES', '[TEMPERATURE', '[EVAP', diff --git a/swmmio/examples.py b/swmmio/examples.py index 53c1445..2eea4e2 100644 --- a/swmmio/examples.py +++ b/swmmio/examples.py @@ -5,7 +5,7 @@ MODEL_PUMP_CONTROL) # example models -philly = Model(MODEL_A_PATH, crs="+init=EPSG:2817") +philly = Model(MODEL_A_PATH, crs="EPSG:2817") jersey = Model(MODEL_FULL_FEATURES_XY) jerzey = Model(MODEL_FULL_FEATURES_XY_B) spruce = Model(MODEL_FULL_FEATURES__NET_PATH) diff --git a/swmmio/tests/test_crs.py b/swmmio/tests/test_crs.py new file mode 100644 index 0000000..86c056a --- /dev/null +++ b/swmmio/tests/test_crs.py @@ -0,0 +1,25 @@ +import pytest + +from pyproj import CRS +from swmmio import Model +from swmmio.tests.data import (MODEL_FULL_FEATURES_PATH) + +def test_crs_initialization_with_epsg(): + model = Model(MODEL_FULL_FEATURES_PATH, crs="EPSG:4326") + assert isinstance(model.crs, CRS) + assert model.crs.to_string() == "EPSG:4326" + +def test_crs_initialization_with_none(): + model = Model(MODEL_FULL_FEATURES_PATH) + assert model.crs is None + +def test_crs_setter_with_epsg(): + model = Model(MODEL_FULL_FEATURES_PATH) + model.crs = "EPSG:4326" + assert isinstance(model.crs, CRS) + assert model.crs.to_string() == "EPSG:4326" + +def test_crs_setter_with_none(): + model = Model(MODEL_FULL_FEATURES_PATH, crs="EPSG:4326") + model.crs = None + assert model.crs is None diff --git a/swmmio/tests/test_dataframes.py b/swmmio/tests/test_dataframes.py index 3e8fe3f..5dc3c39 100644 --- a/swmmio/tests/test_dataframes.py +++ b/swmmio/tests/test_dataframes.py @@ -263,6 +263,20 @@ def test_polygons(test_model_02): # print() +def test_symbols(): + data = { + "Name": ["GAGE1"], + "X": [361.632], + "Y": [267.406] + } + symbols1 = pd.DataFrame(data) + symbols1.set_index('Name', inplace=True) + + m = swmmio.Model(MODEL_GREEN_AMPT) + symbols2 = m.inp.symbols + + assert symbols1.equals(symbols2) + def test_inp_sections(): # Additional models could be added to this test, or additional features diff --git a/swmmio/tests/test_functions.py b/swmmio/tests/test_functions.py index e956d81..627f434 100644 --- a/swmmio/tests/test_functions.py +++ b/swmmio/tests/test_functions.py @@ -6,7 +6,8 @@ from swmmio.tests.data import (MODEL_FULL_FEATURES__NET_PATH, OWA_RPT_EXAMPLE, RPT_FULL_FEATURES, MODEL_EX_1_PARALLEL_LOOP, MODEL_EX_1) -from swmmio.utils.functions import format_inp_section_header, find_network_trace, check_if_url_and_download +from swmmio.utils.functions import (format_inp_section_header, find_network_trace, + check_if_url_and_download, model_to_networkx) from swmmio.utils import error from swmmio.utils.text import get_rpt_metadata @@ -51,6 +52,12 @@ def test_model_to_networkx(): links = m.links.dataframe assert(len(links) == len(G.edges())) +def test_model_to_networkx_crs(): + m = swmmio.Model(MODEL_FULL_FEATURES__NET_PATH, crs="EPSG:4326") + G = model_to_networkx(m) + + assert 'crs' in G.graph + assert G.graph['crs'] == m.crs def test_network_trace_loop(): m = swmmio.Model(MODEL_EX_1_PARALLEL_LOOP)