From 3b1a3a859bac84aa1952a079e8d393ddc8486841 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 05:44:10 -0800 Subject: [PATCH 01/21] Update framework.py --- tools/framework.py | 83 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tools/framework.py b/tools/framework.py index d3b63b84..c68cf9d4 100644 --- a/tools/framework.py +++ b/tools/framework.py @@ -3,6 +3,20 @@ The `framework` module contains the infrastructure to support standardized implementation of tools in GridLAB-D. +Standard options: + +The following options are processed by `read_stdargs()`: + +* `--debug`: enable debug traceback on exception + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--warning`: suppress warning messages + +* `--verbose`: enable verbose output, if any + Example: ~~~ @@ -10,27 +24,38 @@ def main(argv): + # handle no options case -- typically a cry for help if len(argv) == 1: print("\n".join([x for x in __doc__.split("\n") if x.startswith("Syntax: ")])) return app.E_SYNTAX - args = read_stdargs(argv) + # handle stardard app arguments --debug, --warning, --verbose, --quiet, --silent + args = app.read_stdargs(argv) for key,value in args: if key in ["-h","--help","help"]: print(__doc__,file=sys.stdout) + + # TODO: add options here + else: error(f"'{key}={value}' is invalid") return app.E_INVALID + # TODO: code implementation here, if any + return app.E_OK if __name__ == "__main__": try: + # TODO: development testing -- delete when done writing code + if not sys.argv[0]: + sys.argv = ["selftest","--debug"] + rc = main(sys.argv) exit(rc) @@ -138,17 +163,43 @@ def read_stdargs(argv:list[str]) -> list[str]: return result def output(*msg:list,**kwargs): + """General message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are suppressed when the `--silent` option is used. + """ if not "file" in kwargs: kwargs["file"] = sys.stdout if not SILENT: print(*msg,**kwargs) def exception(exc:[TypeVar('Exception')|str]): + """Exception message output + + Arguments: + + * `exc`: exception to raise + """ if isinstance(exc,str): exc = MapError(exc) raise exc def error(*msg:list,code:[int|None]=None,**kwargs): + """Error message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are suppressed when the `--quiet` option is used. + """ if not QUIET: if code: print(f"ERROR [{EXENAME}]: {' '.join([str(x) for x in msg])} (code {repr(code)})",file=sys.stderr,**kwargs) @@ -160,14 +211,44 @@ def error(*msg:list,code:[int|None]=None,**kwargs): sys.exit(code) def verbose(*msg:list,**kwargs): + """Verbose message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are enabled when the `--verbose` option is used. + """ if VERBOSE: print(f"VERBOSE [{EXENAME}]: {' '.join([str(x) for x in msg])}",file=sys.stderr,**kwargs) def warning(*msg:list,**kwargs): + """Warning message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are suppress when the `--warning` option is used. + """ if WARNING: print(f"WARNING [{EXENAME}]: {' '.join([str(x) for x in msg])}",file=sys.stderr,**kwargs) def debug(*msg:list,**kwargs): + """Debugging message output + + Arguments: + + * `msg`: message to output + + * `**kwargs`: print options + + Messages are enabled when the `--debug` option is used. + """ if DEBUG: print(f"DEBUG [{EXENAME}]: {' '.join([str(x) for x in msg])}",file=sys.stderr,**kwargs) From 994fab419bfe999d90d676a92f50de55a769f3dc Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 05:44:13 -0800 Subject: [PATCH 02/21] Create location.py --- tools/location.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tools/location.py diff --git a/tools/location.py b/tools/location.py new file mode 100644 index 00000000..5c7b04ef --- /dev/null +++ b/tools/location.py @@ -0,0 +1,67 @@ +"""Location tool + +Syntax: gridlabd location [OPTIONS ...] + +Options: + +* `--debug`: enable debug traceback on exception + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--warning`: suppress warning messages + +* `--verbose`: enable verbose output, if any + +""" + +import sys +import framework as app + +def main(argv): + + if len(argv) == 1: + + print("\n".join([x for x in __doc__.split("\n") if x.startswith("Syntax: ")])) + return app.E_SYNTAX + + args = app.read_stdargs(argv) + + for key,value in args: + + if key in ["-h","--help","help"]: + print(__doc__,file=sys.stdout) + else: + error(f"'{key}={value}' is invalid") + return app.E_INVALID + + return app.E_OK + +if __name__ == "__main__": + + try: + + # TODO: development testing -- delete when done writing code + if not sys.argv[0]: + sys.argv = ["selftest","--debug"] + + rc = main(sys.argv) + exit(rc) + + except KeyboardInterrupt: + + exit(app.E_INTERRUPT) + + except Exception as exc: + + if DEBUG: + raise exc + + if not QUIET: + e_type,e_value,e_trace = sys.exc_info() + tb = traceback.TracebackException(e_type,e_value,e_trace).stack[1] + print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) + + exit(app.E_EXCEPTION) + From b7ba814884c0a7d592de3fbb499059f1fdde73f2 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 05:49:15 -0800 Subject: [PATCH 03/21] Update framework.py --- tools/framework.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/framework.py b/tools/framework.py index c68cf9d4..ad347ae3 100644 --- a/tools/framework.py +++ b/tools/framework.py @@ -65,12 +65,12 @@ def main(argv): except Exception as exc: - if DEBUG: + if app.DEBUG: raise exc - if not QUIET: + if not app.QUIET: e_type,e_value,e_trace = sys.exc_info() - tb = traceback.TracebackException(e_type,e_value,e_trace).stack[1] + tb = app.traceback.TracebackException(e_type,e_value,e_trace).stack[1] print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) exit(app.E_EXCEPTION) From 92eb52bf090bd4a992cc168472e2b0add915ef65 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 05:49:18 -0800 Subject: [PATCH 04/21] Update location.py --- tools/location.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tools/location.py b/tools/location.py index 5c7b04ef..ef0fdc97 100644 --- a/tools/location.py +++ b/tools/location.py @@ -14,6 +14,9 @@ * `--verbose`: enable verbose output, if any +* `--system[=LOCATION]`: get/set the default location + +* `--find[=LOCATION]`: get location settings """ import sys @@ -32,6 +35,15 @@ def main(argv): if key in ["-h","--help","help"]: print(__doc__,file=sys.stdout) + + elif key in ["--system"]: + + raise NotImplementedError("TODO") + + elif key in ["--find"]: + + raise NotImplementedError("TODO") + else: error(f"'{key}={value}' is invalid") return app.E_INVALID @@ -44,7 +56,7 @@ def main(argv): # TODO: development testing -- delete when done writing code if not sys.argv[0]: - sys.argv = ["selftest","--debug"] + sys.argv = [__file__,"--system"] rc = main(sys.argv) exit(rc) @@ -55,12 +67,12 @@ def main(argv): except Exception as exc: - if DEBUG: + if app.DEBUG: raise exc - if not QUIET: + if not app.QUIET: e_type,e_value,e_trace = sys.exc_info() - tb = traceback.TracebackException(e_type,e_value,e_trace).stack[1] + tb = app.traceback.TracebackException(e_type,e_value,e_trace).stack[1] print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) exit(app.E_EXCEPTION) From e3e6fbc83012b7a1f96864408a2111936af72826 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 05:55:27 -0800 Subject: [PATCH 05/21] Update location.py --- tools/location.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/location.py b/tools/location.py index ef0fdc97..3f2d4946 100644 --- a/tools/location.py +++ b/tools/location.py @@ -20,6 +20,7 @@ """ import sys +import json import framework as app def main(argv): @@ -38,7 +39,15 @@ def main(argv): elif key in ["--system"]: - raise NotImplementedError("TODO") + if not value: + + data = json.loads(app.gridlabd("--globals=json").stdout.decode('utf-8')) + for item in ["city","region","county","state","country"]: + print(f"{item}: {data[item]['value'] if item in data else '(none)'}") + + else: + + raise NotImplementedError("TODO") elif key in ["--find"]: From f7b708acc43b04652338ab8a86a3b7d0eea1a4f8 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 05:55:29 -0800 Subject: [PATCH 06/21] Create test_location.glm --- tools/autotest/test_location.glm | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/autotest/test_location.glm diff --git a/tools/autotest/test_location.glm b/tools/autotest/test_location.glm new file mode 100644 index 00000000..b1699c54 --- /dev/null +++ b/tools/autotest/test_location.glm @@ -0,0 +1 @@ +#system gridlabd location --system From b2e16e1bb64c3451cf0c13600c2791c66387f9f9 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 06:10:36 -0800 Subject: [PATCH 07/21] Fix location autotest --- docs/Tools/Framework.md | 119 ++++++++++++++++++++++++++++++++++++++-- docs/Tools/Location.md | 21 +++++++ docs/Tools/Makefile.mk | 1 + docs/Tools/Mapping.md | 86 +++++++++++++++++++++++++++++ tools/Makefile.mk | 1 + 5 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 docs/Tools/Location.md diff --git a/docs/Tools/Framework.md b/docs/Tools/Framework.md index 5675da66..5647806f 100644 --- a/docs/Tools/Framework.md +++ b/docs/Tools/Framework.md @@ -3,6 +3,20 @@ The `framework` module contains the infrastructure to support standardized implementation of tools in GridLAB-D. +Standard options: + +The following options are processed by `read_stdargs()`: + +* `--debug`: enable debug traceback on exception + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--warning`: suppress warning messages + +* `--verbose`: enable verbose output, if any + Example: ~~~ @@ -10,6 +24,7 @@ import framework as app def main(argv): + # handle no options case -- typically a cry for help if len(argv) == 1: print(" @@ -17,22 +32,32 @@ def main(argv): ") if x.startswith("Syntax: ")])) return app.E_SYNTAX - args = read_stdargs(argv) + # handle stardard app arguments --debug, --warning, --verbose, --quiet, --silent + args = app.read_stdargs(argv) for key,value in args: if key in ["-h","--help","help"]: print(__doc__,file=sys.stdout) + + # TODO: add options here + else: error(f"'{key}={value}' is invalid") return app.E_INVALID + # TODO: code implementation here, if any + return app.E_OK if __name__ == "__main__": try: + # TODO: development testing -- delete when done writing code + if not sys.argv[0]: + sys.argv = ["selftest","--debug"] + rc = main(sys.argv) exit(rc) @@ -42,12 +67,12 @@ if __name__ == "__main__": except Exception as exc: - if DEBUG: + if app.DEBUG: raise exc - if not QUIET: + if not app.QUIET: e_type,e_value,e_trace = sys.exc_info() - tb = traceback.TracebackException(e_type,e_value,e_trace).stack[1] + tb = app.traceback.TracebackException(e_type,e_value,e_trace).stack[1] print(f"EXCEPTION [{app.EXEFILE}@{tb.lineno}]: ({e_type.__name__}) {e_value}",file=sys.stderr) exit(app.E_EXCEPTION) @@ -81,6 +106,21 @@ Returns: * `complex`: complex value (if form is 'conjugate' or None) +--- + +## `debug() -> None` + +Debugging message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--debug` option is used. + + --- ## `double_unit() -> float` @@ -94,6 +134,32 @@ Returns: * real value +--- + +## `error() -> None` + +Error message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--quiet` option is used. + + +--- + +## `exception() -> None` + +Exception message output + +Arguments: + +* `exc`: exception to raise + + --- ## `gridlabd() -> Optional` @@ -153,6 +219,21 @@ Return: * File handle to JSON file after conversion from GLM +--- + +## `output() -> None` + +General message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--silent` option is used. + + --- ## `read_stdargs() -> list` @@ -168,6 +249,21 @@ Returns: * Remaining arguments +--- + +## `verbose() -> None` + +Verbose message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--verbose` option is used. + + --- ## `version() -> str` @@ -178,3 +274,18 @@ Returns: * GridLAB-D version info + +--- + +## `warning() -> None` + +Warning message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppress when the `--warning` option is used. + diff --git a/docs/Tools/Location.md b/docs/Tools/Location.md new file mode 100644 index 00000000..3612f5f1 --- /dev/null +++ b/docs/Tools/Location.md @@ -0,0 +1,21 @@ +[[/Tools/Location]] -- Location tool + +Syntax: gridlabd location [OPTIONS ...] + +Options: + +* `--debug`: enable debug traceback on exception + +* `--quiet`: suppress error messages + +* `--silent`: suppress all error messages + +* `--warning`: suppress warning messages + +* `--verbose`: enable verbose output, if any + +* `--system[=LOCATION]`: get/set the default location + +* `--find[=LOCATION]`: get location settings + + diff --git a/docs/Tools/Makefile.mk b/docs/Tools/Makefile.mk index 705d73fa..9333cf4c 100644 --- a/docs/Tools/Makefile.mk +++ b/docs/Tools/Makefile.mk @@ -1,6 +1,7 @@ DOCS_UTILITIES += docs/Tools/Framework.md DOCS_UTILITIES += docs/Tools/Network.md DOCS_UTILITIES += docs/Tools/Edit.md +DOCS_UTILITIES += docs/Tools/Location.md DOCS_UTILITIES += docs/Tools/Mapping.md DOCS_UTILITIES += docs/Tools/Moutils.md DOCS_UTILITIES += docs/Tools/Unitcalc.md diff --git a/docs/Tools/Mapping.md b/docs/Tools/Mapping.md index 69b4cdd5..8d9fe305 100644 --- a/docs/Tools/Mapping.md +++ b/docs/Tools/Mapping.md @@ -202,6 +202,21 @@ Returns: * `complex`: complex value (if form is 'conjugate' or None) +--- + +## `debug() -> None` + +Debugging message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--debug` option is used. + + --- ## `double_unit() -> float` @@ -215,6 +230,32 @@ Returns: * real value +--- + +## `error() -> None` + +Error message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--quiet` option is used. + + +--- + +## `exception() -> None` + +Exception message output + +Arguments: + +* `exc`: exception to raise + + --- ## `get_options() -> dict` @@ -306,6 +347,21 @@ Return: * File handle to JSON file after conversion from GLM +--- + +## `output() -> None` + +General message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppressed when the `--silent` option is used. + + --- ## `read_stdargs() -> list` @@ -321,6 +377,21 @@ Returns: * Remaining arguments +--- + +## `verbose() -> None` + +Verbose message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are enabled when the `--verbose` option is used. + + --- ## `version() -> str` @@ -331,3 +402,18 @@ Returns: * GridLAB-D version info + +--- + +## `warning() -> None` + +Warning message output + +Arguments: + +* `msg`: message to output + +* `**kwargs`: print options + +Messages are suppress when the `--warning` option is used. + diff --git a/tools/Makefile.mk b/tools/Makefile.mk index fa086148..012e0cb1 100644 --- a/tools/Makefile.mk +++ b/tools/Makefile.mk @@ -22,6 +22,7 @@ dist_pkgdata_DATA += tools/group.py dist_pkgdata_DATA += tools/insights.py dist_pkgdata_DATA += tools/install.py dist_pkgdata_DATA += tools/isone.py +dist_pkgdata_DATA += tools/location.py dist_pkgdata_DATA += tools/mapping.py dist_pkgdata_DATA += tools/mapping_config.py dist_pkgdata_DATA += tools/market_data.py From b09876275c84b1fbf9327c0e6087915f06e326fa Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 06:45:51 -0800 Subject: [PATCH 08/21] Update location.py --- tools/location.py | 72 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/tools/location.py b/tools/location.py index 3f2d4946..bf131d49 100644 --- a/tools/location.py +++ b/tools/location.py @@ -22,6 +22,7 @@ import sys import json import framework as app +import geocoder def main(argv): @@ -32,18 +33,70 @@ def main(argv): args = app.read_stdargs(argv) + def output_raw(data,**kwargs): + print(data,**kwargs) + + def output_csv(data,**kwargs): + if isinstance(data,list): + print("\n".join(data)) + elif isinstance(data,dict): + print("\n".join([f"{x},{y}" for x,y in data.items()])) + else: + raise ResourceError(f"unable to output '{type(data)}' as CSV") + + def output_json(data,**kwargs): + print(json.dumps(data,**kwargs)) + + outputter = output_raw + outputter_options = {} + for key,value in args: if key in ["-h","--help","help"]: print(__doc__,file=sys.stdout) + elif key in ["--format"]: + + if len(value) == 0: + + app.error("missing format") + return app.E_MISSING + + + elif value[0] == "csv": + + if len(value) > 1: + app.error(f"invalid format options '{','.join(value[1:])}'") + return app.E_INVALID + outputter = output_csv + + elif value[0] == "json": + + options = {x:y for x,y in [z.split(":",1) for z in value[1:]]} if len(value) > 1 else {} + _bool = lambda x: x=="true" if x in ["true","false"] else None, + for x,y in { + "indent": int, + "skipkeys": _bool, + "ensure_ascii": _bool, + "check_circular": _bool, + "allow_nan": _bool, + "sort_keys": _bool, + }.items(): + try: + options[x] = y(options[x]) + except: + pass + outputter = output_json + outputter_options = options + elif key in ["--system"]: if not value: data = json.loads(app.gridlabd("--globals=json").stdout.decode('utf-8')) - for item in ["city","region","county","state","country"]: - print(f"{item}: {data[item]['value'] if item in data else '(none)'}") + result = {} + for item in ["city","county","state","region","country"]: + result[item] = data[item]['value'] if item in data else "" else: @@ -51,12 +104,23 @@ def main(argv): elif key in ["--find"]: - raise NotImplementedError("TODO") + if not value: + + data = geocoder.ip('me') + result = {} + for item in ["city","county","state","region","country"]: + result[item] = getattr(data,item) if hasattr(data,item) else "" + + else: + + raise NotImplementedError("TODO") else: error(f"'{key}={value}' is invalid") return app.E_INVALID + outputter(result,**outputter_options) + return app.E_OK if __name__ == "__main__": @@ -65,7 +129,7 @@ def main(argv): # TODO: development testing -- delete when done writing code if not sys.argv[0]: - sys.argv = [__file__,"--system"] + sys.argv = [__file__,"--format=json,indent:4","--find"] rc = main(sys.argv) exit(rc) From 5410fcf87f1913e6d01509f242e1b214c1d34297 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 07:27:57 -0800 Subject: [PATCH 09/21] Update location.py --- tools/location.py | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/tools/location.py b/tools/location.py index bf131d49..92d93966 100644 --- a/tools/location.py +++ b/tools/location.py @@ -1,6 +1,6 @@ """Location tool -Syntax: gridlabd location [OPTIONS ...] +Syntax: gridlabd location [OPTIONS ...] [FILENAME KEY[=VALUE] ...] Options: @@ -17,6 +17,41 @@ * `--system[=LOCATION]`: get/set the default location * `--find[=LOCATION]`: get location settings + +Description: + +The `location` tool allows configuration of the location of a model. + +The `location` tool `--system` option is used to setup the system's default +location for models when not location data is not specified in the model. + +The `location` tool `--find` options can identify the current location of a +system or a location based on partial information. + +The keys and globals handled by the `location` tools include the following: + +* `latitude`: the location's latitude + +* `longitude`: the location's longitude + +* `number`: the location's street number, if any + +* `street`: the location's street name + +* `zipcode`: the location's postal code + +* `city`: the location's city + +* `county`: the location's county + +* `state`: the location's state + +* `region`: the location's region + +* `country`: the location's country + +The number + """ import sys @@ -24,6 +59,8 @@ import framework as app import geocoder +location_keys = ["latitude","longitude","number","street","zipcode","city","county","state","region","country"] + def main(argv): if len(argv) == 1: @@ -95,7 +132,7 @@ def output_json(data,**kwargs): data = json.loads(app.gridlabd("--globals=json").stdout.decode('utf-8')) result = {} - for item in ["city","county","state","region","country"]: + for item in location_keys: result[item] = data[item]['value'] if item in data else "" else: @@ -108,13 +145,15 @@ def output_json(data,**kwargs): data = geocoder.ip('me') result = {} - for item in ["city","county","state","region","country"]: + for item in location_keys: result[item] = getattr(data,item) if hasattr(data,item) else "" else: raise NotImplementedError("TODO") + + else: error(f"'{key}={value}' is invalid") return app.E_INVALID From fa3eda36379bd7105e6a50115d66aa28f1c7ff38 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 07:55:00 -0800 Subject: [PATCH 10/21] Update location.py Signed-off-by: David P. Chassin --- tools/location.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tools/location.py b/tools/location.py index 92d93966..75751eb9 100644 --- a/tools/location.py +++ b/tools/location.py @@ -1,6 +1,6 @@ """Location tool -Syntax: gridlabd location [OPTIONS ...] [FILENAME KEY[=VALUE] ...] +Syntax: `gridlabd location [OPTIONS ...] [FILENAME=KEY[:VALUE][,...] ...]` Options: @@ -50,8 +50,19 @@ * `country`: the location's country -The number +Examples: +Get the current location + + gridlabd location --find + +Display the default location + + gridlabd location --system + +Set the location in a model file + + gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield """ import sys From 04346981bb60b4c315f4227d833abb7f58698f36 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 14:24:13 -0800 Subject: [PATCH 11/21] Update --system options --- docs/Tools/Location.md | 51 +++++++++++++++++++++++++++++++++++++++++- runtime/gridlabd.conf | 13 +++++------ tools/location.py | 36 ++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/docs/Tools/Location.md b/docs/Tools/Location.md index 3612f5f1..6aad4cb8 100644 --- a/docs/Tools/Location.md +++ b/docs/Tools/Location.md @@ -1,6 +1,6 @@ [[/Tools/Location]] -- Location tool -Syntax: gridlabd location [OPTIONS ...] +Syntax: `gridlabd location [OPTIONS ...] [FILENAME=KEY[:VALUE][,...] ...]` Options: @@ -18,4 +18,53 @@ Options: * `--find[=LOCATION]`: get location settings +Description: + +The `location` tool allows configuration of the location of a model. + +The `location` tool `--system` option is used to setup the system's default +location for models when not location data is not specified in the model. +When values are change, the location data is returned and the new location +is stored in `GLD_ETC/location_config.glm + +The `location` tool `--find` options can identify the current location of a +system or a location based on partial information. + +The keys and globals handled by the `location` tools include the following: + +* `latitude`: the location's latitude + + +* `longitude`: the location's longitude + +* `number`: the location's street number, if any + +* `street`: the location's street name + +* `zipcode`: the location's postal code + +* `city`: the location's city + +* `county`: the location's county + +* `state`: the location's state + +* `region`: the location's region + +* `country`: the location's country + +Examples: + +Get the current location + + gridlabd location --find + +Display the default location + + gridlabd location --system + +Set the location in a model file + + gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield + diff --git a/runtime/gridlabd.conf b/runtime/gridlabd.conf index 51097fd6..7b3b91b3 100644 --- a/runtime/gridlabd.conf +++ b/runtime/gridlabd.conf @@ -8,7 +8,7 @@ #ifdef WINDOWS #setenv GRIDLABD=c:/Program Files/GridLAB-D #else // linux/mac -#setenv GRIDLABD=/usr/etc/gridlabd +#setenv GRIDLABD=/usr/local/opt/gridlabd/current #endif #endif // GRIDLABD @@ -37,13 +37,10 @@ #endif // gnuplot ///////////////////////////////////////////////////////////////////////////// -// The following are some recommended settings -// set the save default file name -// comment it out to automatic save to this file -//#set savefile=gridlabd.xml - -// comment this out to stop using the PNNL's development URL base -//#set urlbase=http://gridlabd.pnl.gov/source/VS2005 +// location configuration +#ifexist "location.glm" +#include "location.glm" +#endif // location ///////////////////////////////////////////////////////////////////////////// // get user/custom settings diff --git a/tools/location.py b/tools/location.py index 75751eb9..0ba8fecc 100644 --- a/tools/location.py +++ b/tools/location.py @@ -24,6 +24,8 @@ The `location` tool `--system` option is used to setup the system's default location for models when not location data is not specified in the model. +When values are change, the location data is returned and the new location +is stored in `GLD_ETC/location_config.glm The `location` tool `--find` options can identify the current location of a system or a location based on partial information. @@ -32,6 +34,7 @@ * `latitude`: the location's latitude + * `longitude`: the location's longitude * `number`: the location's street number, if any @@ -65,8 +68,10 @@ gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield """ +import os import sys import json +import datetime as dt import framework as app import geocoder @@ -139,16 +144,24 @@ def output_json(data,**kwargs): elif key in ["--system"]: - if not value: - - data = json.loads(app.gridlabd("--globals=json").stdout.decode('utf-8')) - result = {} - for item in location_keys: - result[item] = data[item]['value'] if item in data else "" + glm = os.path.join(os.environ["GLD_ETC"],"location.glm") + data = json.loads(app.gridlabd(glm,"--globals=json").stdout.decode('utf-8')) + result = {} + for item in location_keys: + result[item] = data[item]['value'] if item in data else "" - else: - - raise NotImplementedError("TODO") + save = dict(result) + for x,y in [x.split(":",1) for x in value]: + if not x in save: + error(f"'{x}' is not a valid location key") + return app.E_INVALID + save[x] = y + if save != result: + with open(glm,"w") as fh: + print(f"// generated by {' '.join(sys.argv)} on {dt.datetime.now()}",file=fh) + for x,y in save.items(): + setter = "set" if x in data else "define" + print(f'#{setter} {x}="{y}"',file=fh) elif key in ["--find"]: @@ -169,7 +182,8 @@ def output_json(data,**kwargs): error(f"'{key}={value}' is invalid") return app.E_INVALID - outputter(result,**outputter_options) + if result: + outputter(result,**outputter_options) return app.E_OK @@ -179,7 +193,7 @@ def output_json(data,**kwargs): # TODO: development testing -- delete when done writing code if not sys.argv[0]: - sys.argv = [__file__,"--format=json,indent:4","--find"] + sys.argv = [__file__,"--system=number:2575,street:Sand Hill Rd,city:Menlo Park,state:CA,region:west,country:US"] rc = main(sys.argv) exit(rc) From 31dd286125b47cd0bab95820abd3842185178ce4 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 15:09:25 -0800 Subject: [PATCH 12/21] Update location.py --- tools/location.py | 151 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 33 deletions(-) diff --git a/tools/location.py b/tools/location.py index 0ba8fecc..10151a03 100644 --- a/tools/location.py +++ b/tools/location.py @@ -6,17 +6,19 @@ * `--debug`: enable debug traceback on exception +* `--find[=LOCATION]`: get location settings + +* `--format=FORMAT[,OPTION[:VALUE[,...]]] + * `--quiet`: suppress error messages * `--silent`: suppress all error messages -* `--warning`: suppress warning messages +* `--system[=LOCATION]`: get/set the default location * `--verbose`: enable verbose output, if any -* `--system[=LOCATION]`: get/set the default location - -* `--find[=LOCATION]`: get location settings +* `--warning`: suppress warning messages Description: @@ -30,6 +32,10 @@ The `location` tool `--find` options can identify the current location of a system or a location based on partial information. +Location setting on `FILENAME` will be performed in place, i.e., the file will +first be read and the it will be written with the new values. The result +output to stdout will be the previous values. + The keys and globals handled by the `location` tools include the following: * `latitude`: the location's latitude @@ -74,11 +80,105 @@ import datetime as dt import framework as app import geocoder +import edit location_keys = ["latitude","longitude","number","street","zipcode","city","county","state","region","country"] -def main(argv): +def system(**kwargs) -> dict: + """Get/set system location settings + + Arguments: + + * `latitude`: new latitude + + * `longitude`: new longitude + + * `number`: new street number + + * `street`: new street name + + * `zipcode`: new zipcode + + * `city`: new city + + * `county`: new county + + * `state`: new state + + * `region`: new region + + * `country`: new country + + Returns: + + * previous location settings + """ + glm = os.path.join(os.environ["GLD_ETC"],"location.glm") + data = json.loads(app.gridlabd(glm,"--globals=json").stdout.decode('utf-8')) + result = {} + for item in location_keys: + result[item] = data[item]['value'] if item in data else "" + + save = dict(result) + for x,y in kwargs.items(): + if not x in save: + error(f"'{x}' is not a valid location key") + return app.E_INVALID + save[x] = y + + if save != result: + with open(glm,"w") as fh: + print(f"// generated by {' '.join(sys.argv)} on {dt.datetime.now()}",file=fh) + for x,y in save.items(): + setter = "set" if x in data else "define" + print(f'#{setter} {x}="{y}"',file=fh) + return result + +def find(**kwargs) -> dict: + """Find location data + + Arguments: + + * `kwargs`: Partial location data (see `system()`). None return IP location. + + Returns: + + * Location data + """ + if not kwargs: + + data = geocoder.ip('me') + result = {} + for item in location_keys: + result[item] = getattr(data,item) if hasattr(data,item) else "" + + else: + + raise NotImplementedError("TODO") + + return result + +def set_location(file,**kwargs): + """TODO""" + raise NotImplementedError("TODO") +def get_location(file): + """TODO""" + data = json.load(open(file,"r")) + result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in location_keys} + return result + +def main(argv:list) -> int: + """Main location routine + + Arguments: + + * `argv`: command line argument list + + Returns: + + * Exit code + """ if len(argv) == 1: print("\n".join([x for x in __doc__.split("\n") if x.startswith("Syntax: ")])) @@ -144,39 +244,21 @@ def output_json(data,**kwargs): elif key in ["--system"]: - glm = os.path.join(os.environ["GLD_ETC"],"location.glm") - data = json.loads(app.gridlabd(glm,"--globals=json").stdout.decode('utf-8')) - result = {} - for item in location_keys: - result[item] = data[item]['value'] if item in data else "" - - save = dict(result) - for x,y in [x.split(":",1) for x in value]: - if not x in save: - error(f"'{x}' is not a valid location key") - return app.E_INVALID - save[x] = y - if save != result: - with open(glm,"w") as fh: - print(f"// generated by {' '.join(sys.argv)} on {dt.datetime.now()}",file=fh) - for x,y in save.items(): - setter = "set" if x in data else "define" - print(f'#{setter} {x}="{y}"',file=fh) + options = dict([x.split(":",1) for x in value]) + result = system(**options) elif key in ["--find"]: - if not value: + options = dict([x.split(":",1) for x in value]) + result = find(**options) - data = geocoder.ip('me') - result = {} - for item in location_keys: - result[item] = getattr(data,item) if hasattr(data,item) else "" + elif os.path.exists(key): + if not value: + result = get_location(key) else: - - raise NotImplementedError("TODO") - - + options = dict([x.split(":",1) for x in value]) + result = set_location(key,**options) else: error(f"'{key}={value}' is invalid") @@ -193,7 +275,10 @@ def output_json(data,**kwargs): # TODO: development testing -- delete when done writing code if not sys.argv[0]: - sys.argv = [__file__,"--system=number:2575,street:Sand Hill Rd,city:Menlo Park,state:CA,region:west,country:US"] + # sys.argv = [__file__,"--system=number:2575,street:Sand Hill Rd,city:Menlo Park,state:CA,region:west,country:US"] + # sys.argv = [__file__,"--find"] + # sys.argv = [__file__,"autotest/test_moutils.json"] + sys.argv = [__file__,"autotest/test_moutils.json=city:Seattle"] rc = main(sys.argv) exit(rc) From d485c6f3c79a2309c7dd565b76c57a0a21d2ea95 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 15:09:27 -0800 Subject: [PATCH 13/21] Update edit.py --- tools/edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/edit.py b/tools/edit.py index 8c9eb09b..3d0e3b56 100644 --- a/tools/edit.py +++ b/tools/edit.py @@ -163,7 +163,7 @@ class Editor: """GLM Model Editor """ def __init__(self,data:dict): - assert "application" in data, "data does not valid application data" + assert "application" in data, "data does not have valid application data" assert data["application"] == "gridlabd", "data is not a valid gridlabd model" self.data = data From d9ed269e19e52f0583b9bca8a1afa44234e61408 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 15 Jan 2025 15:13:37 -0800 Subject: [PATCH 14/21] Update location.py --- tools/location.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/location.py b/tools/location.py index 10151a03..0a7e8b2e 100644 --- a/tools/location.py +++ b/tools/location.py @@ -6,7 +6,7 @@ * `--debug`: enable debug traceback on exception -* `--find[=LOCATION]`: get location settings +* `--find[=KEY[:VALUE][,...]]`: get location settings * `--format=FORMAT[,OPTION[:VALUE[,...]]] @@ -14,7 +14,7 @@ * `--silent`: suppress all error messages -* `--system[=LOCATION]`: get/set the default location +* `--system[=KEY[:VALUE][,...]]`: get/set the default location * `--verbose`: enable verbose output, if any From 75688150403b8517c23438bd0797fc6b02e6d062 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 09:41:26 -0600 Subject: [PATCH 15/21] Update location.py --- tools/location.py | 90 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/tools/location.py b/tools/location.py index 0a7e8b2e..f06a5266 100644 --- a/tools/location.py +++ b/tools/location.py @@ -40,13 +40,8 @@ * `latitude`: the location's latitude - * `longitude`: the location's longitude -* `number`: the location's street number, if any - -* `street`: the location's street name - * `zipcode`: the location's postal code * `city`: the location's city @@ -82,7 +77,22 @@ import geocoder import edit -location_keys = ["latitude","longitude","number","street","zipcode","city","county","state","region","country"] +LOCATIONKEYS = ["latitude","longitude","city","zipcode","county","state","region","country"] +PROVIDER = "arcgis" +PROVIDERCONFIG = { + "arcgis" : { + "lat" : "latitude", + "lng" : "longitude", + "city" : "city", + "postal" : "zipcode", + "raw.address.RegionAbbr" : "state", + "raw.address.Subregion" : "county", + "country" : "country", + } +} + +class LocationError(Exception): + """Location exception""" def system(**kwargs) -> dict: """Get/set system location settings @@ -116,14 +126,13 @@ def system(**kwargs) -> dict: glm = os.path.join(os.environ["GLD_ETC"],"location.glm") data = json.loads(app.gridlabd(glm,"--globals=json").stdout.decode('utf-8')) result = {} - for item in location_keys: + for item in LOCATIONKEYS: result[item] = data[item]['value'] if item in data else "" save = dict(result) for x,y in kwargs.items(): if not x in save: - error(f"'{x}' is not a valid location key") - return app.E_INVALID + raise LocationError(f"'{x}' is not a valid location key") save[x] = y if save != result: @@ -134,7 +143,7 @@ def system(**kwargs) -> dict: print(f'#{setter} {x}="{y}"',file=fh) return result -def find(**kwargs) -> dict: +def find(*address) -> dict: """Find location data Arguments: @@ -145,27 +154,52 @@ def find(**kwargs) -> dict: * Location data """ - if not kwargs: + if len(address) == 0 or not address[0]: data = geocoder.ip('me') result = {} - for item in location_keys: + for item in LOCATIONKEYS: result[item] = getattr(data,item) if hasattr(data,item) else "" - - else: - - raise NotImplementedError("TODO") + if not result: + raise LocationError("unable to find current location") + address = (f"{result['city']}, {result['state']}, {result['country']}",) + + result = [] + for item in address: + location = getattr(geocoder,PROVIDER)(address) + data = getattr(geocoder,PROVIDER)(location.latlng,method='reverse') + if data.ok: + found = {} + for key,value in PROVIDERCONFIG[PROVIDER].items(): + for subkey in key.split("."): + found[value] = (found[value] if value in found else data.json)[subkey] + for key,value in found.items(): + found[key] = str(value) + result.append(found) + else: + raise LocationError("no location found") return result def set_location(file,**kwargs): """TODO""" - raise NotImplementedError("TODO") + data = json.load(open(file,"r")) + result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in LOCATIONKEYS} + for key,value in kwargs.items(): + if not key in LOCATIONKEYS: + raise LocationError(f"'{key}' is not a valid location key") + data["globals"][key] = { + "type" : "char32", + "access" : "public", + "value" : value, + } + json.dump(data,open(file,"w"),indent=4) + return result def get_location(file): """TODO""" data = json.load(open(file,"r")) - result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in location_keys} + result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in LOCATIONKEYS} return result def main(argv:list) -> int: @@ -203,6 +237,7 @@ def output_json(data,**kwargs): outputter = output_raw outputter_options = {} + result = [] for key,value in args: if key in ["-h","--help","help"]: @@ -249,8 +284,8 @@ def output_json(data,**kwargs): elif key in ["--find"]: - options = dict([x.split(":",1) for x in value]) - result = find(**options) + address = [",".join(value)] + result.extend(find(*address)) elif os.path.exists(key): @@ -273,12 +308,15 @@ def output_json(data,**kwargs): try: - # TODO: development testing -- delete when done writing code - if not sys.argv[0]: - # sys.argv = [__file__,"--system=number:2575,street:Sand Hill Rd,city:Menlo Park,state:CA,region:west,country:US"] - # sys.argv = [__file__,"--find"] - # sys.argv = [__file__,"autotest/test_moutils.json"] - sys.argv = [__file__,"autotest/test_moutils.json=city:Seattle"] + # TODO: development testing -- only needed when developing/debugging + # if not sys.argv[0]: + # # sys.argv = [__file__,"--system"] + # # sys.argv = [__file__,"--system=city:Menlo Park,state:CA,region:west,country:US"] + # # sys.argv = [__file__,"--find"] + # # sys.argv = [__file__,"--find=2575 Sand Hill Rd, Menlo Park, CA, USA"] + # # sys.argv = [__file__,"--find=2575 Sand Hill Rd, Menlo Park, CA","--find=7443 87th Dr NE, Marysville, WA"] + # # sys.argv = [__file__,"autotest/test_moutils.json"] + # # sys.argv = [__file__,"autotest/test_moutils.json=city:Seattle"] rc = main(sys.argv) exit(rc) From fd61da0faec28dfda0408f98ddede3553f738e95 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 09:41:28 -0600 Subject: [PATCH 16/21] Update Location.md --- docs/Tools/Location.md | 89 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/docs/Tools/Location.md b/docs/Tools/Location.md index 6aad4cb8..69f60479 100644 --- a/docs/Tools/Location.md +++ b/docs/Tools/Location.md @@ -6,17 +6,19 @@ Options: * `--debug`: enable debug traceback on exception +* `--find[=KEY[:VALUE][,...]]`: get location settings + +* `--format=FORMAT[,OPTION[:VALUE[,...]]] + * `--quiet`: suppress error messages * `--silent`: suppress all error messages -* `--warning`: suppress warning messages +* `--system[=KEY[:VALUE][,...]]`: get/set the default location * `--verbose`: enable verbose output, if any -* `--system[=LOCATION]`: get/set the default location - -* `--find[=LOCATION]`: get location settings +* `--warning`: suppress warning messages Description: @@ -30,6 +32,10 @@ is stored in `GLD_ETC/location_config.glm The `location` tool `--find` options can identify the current location of a system or a location based on partial information. +Location setting on `FILENAME` will be performed in place, i.e., the file will +first be read and the it will be written with the new values. The result +output to stdout will be the previous values. + The keys and globals handled by the `location` tools include the following: * `latitude`: the location's latitude @@ -68,3 +74,78 @@ Set the location in a model file gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield + +# Functions + +## `find() -> dict` + +Find location data + +Arguments: + +* `kwargs`: Partial location data (see `system()`). None return IP location. + +Returns: + +* Location data + + +--- + +## `get_location() -> None` + +TODO + +--- + +## `main() -> int` + +Main location routine + +Arguments: + +* `argv`: command line argument list + +Returns: + +* Exit code + + +--- + +## `set_location() -> None` + +TODO + +--- + +## `system() -> dict` + +Get/set system location settings + +Arguments: + +* `latitude`: new latitude + +* `longitude`: new longitude + +* `number`: new street number + +* `street`: new street name + +* `zipcode`: new zipcode + +* `city`: new city + +* `county`: new county + +* `state`: new state + +* `region`: new region + +* `country`: new country + +Returns: + +* previous location settings + From 547aebf9bd07e09c70e0039836b3236be3ffd7d7 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 09:43:39 -0600 Subject: [PATCH 17/21] Update test_location.glm --- tools/autotest/test_location.glm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/autotest/test_location.glm b/tools/autotest/test_location.glm index b1699c54..894e4363 100644 --- a/tools/autotest/test_location.glm +++ b/tools/autotest/test_location.glm @@ -1 +1,6 @@ -#system gridlabd location --system +#system rm -f ${GLD_ETC}/location.glm +#system gridlabd location --system > test_location.txt + +#ifexist "../test_location.txt" +#on_exit 0 diff ../test_location.txt test_location.txt > gridlabd.diff +#endif From 445feeae2533988bbfc5acc9d9bcfc55ac0d3658 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 09:43:42 -0600 Subject: [PATCH 18/21] Create test_location.txt --- tools/autotest/test_location.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/autotest/test_location.txt diff --git a/tools/autotest/test_location.txt b/tools/autotest/test_location.txt new file mode 100644 index 00000000..a0427ed8 --- /dev/null +++ b/tools/autotest/test_location.txt @@ -0,0 +1 @@ +{'latitude': '', 'longitude': '', 'number': '', 'street': '', 'zipcode': '', 'city': '', 'county': '', 'state': '', 'region': 'CA', 'country': 'US'} From 20566452de006b889805913889b373290872aee6 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 10:13:38 -0600 Subject: [PATCH 19/21] Update Location.md --- docs/Tools/Location.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/Tools/Location.md b/docs/Tools/Location.md index 69f60479..0a265205 100644 --- a/docs/Tools/Location.md +++ b/docs/Tools/Location.md @@ -40,13 +40,8 @@ The keys and globals handled by the `location` tools include the following: * `latitude`: the location's latitude - * `longitude`: the location's longitude -* `number`: the location's street number, if any - -* `street`: the location's street name - * `zipcode`: the location's postal code * `city`: the location's city @@ -75,6 +70,12 @@ Set the location in a model file +# Classes + +## LocationError + +Location exception + # Functions ## `find() -> dict` From edd5d73c475e7e440c7af45a1bc9bfb8d26bdf31 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 10:13:41 -0600 Subject: [PATCH 20/21] Update test_location.txt --- tools/autotest/test_location.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/autotest/test_location.txt b/tools/autotest/test_location.txt index a0427ed8..c4198f0b 100644 --- a/tools/autotest/test_location.txt +++ b/tools/autotest/test_location.txt @@ -1 +1 @@ -{'latitude': '', 'longitude': '', 'number': '', 'street': '', 'zipcode': '', 'city': '', 'county': '', 'state': '', 'region': 'CA', 'country': 'US'} +{'latitude': '', 'longitude': '', 'city': '', 'zipcode': '', 'county': '', 'state': '', 'region': 'CA', 'country': 'US'} From 2bfc1a5dc9933fe704fed3cec805745c3c418b89 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 16 Jan 2025 11:32:20 -0600 Subject: [PATCH 21/21] Fix autotest error --- docs/Tools/Location.md | 36 +++++++++++-- tools/location.py | 117 +++++++++++++++++++++++++++++++++-------- 2 files changed, 127 insertions(+), 26 deletions(-) diff --git a/docs/Tools/Location.md b/docs/Tools/Location.md index 0a265205..f75d3dcd 100644 --- a/docs/Tools/Location.md +++ b/docs/Tools/Location.md @@ -54,6 +54,13 @@ The keys and globals handled by the `location` tools include the following: * `country`: the location's country +Caveat: + +Although the `--find` option allows multiple addresses to be resolved, it is +certainly not efficient to do more than a few queries this way. If you need +to resolve large number of addresses then you should use the batch feature of +the `geocoder` module. + Examples: Get the current location @@ -70,6 +77,7 @@ Set the location in a model file + # Classes ## LocationError @@ -93,9 +101,18 @@ Returns: --- -## `get_location() -> None` +## `get_location() -> dict` + +Get location data in file + +Arguments: + +* `file`: file from which to get location data + +Returns: + +* Current values -TODO --- @@ -114,9 +131,20 @@ Returns: --- -## `set_location() -> None` +## `set_location() -> dict` + +Set location in file + +Arguments: + +* `file`: file in which to set location data + +* `**kwargs`: location data + +Returns: + +* Previous values -TODO --- diff --git a/tools/location.py b/tools/location.py index f06a5266..ff27da84 100644 --- a/tools/location.py +++ b/tools/location.py @@ -54,6 +54,13 @@ * `country`: the location's country +Caveat: + +Although the `--find` option allows multiple addresses to be resolved, it is +certainly not efficient to do more than a few queries this way. If you need +to resolve large number of addresses then you should use the batch feature of +the `geocoder` module. + Examples: Get the current location @@ -67,6 +74,7 @@ Set the location in a model file gridlabd location ieee123.json=country:US,state:CA,county:Kern,city:Bakersfield + """ import os @@ -94,7 +102,35 @@ class LocationError(Exception): """Location exception""" -def system(**kwargs) -> dict: +class Location: + + def __init__(self,**kwargs): + + for key,value in kwargs.items(): + if not key in LOCATIONKEYS: + raise LocationError(f"'{key}' is not a valid location key") + setattr(self,key,value) + for key in LOCATIONKEYS: + if not hasattr(self,key): + setattr(self,key,"") + + def __repr__(self): + return str(dict(self.items())) + + def __getitem__(self,key): + + return getattr(self,key) + + def keys(self): + return LOCATIONKEYS + + def values(self): + return [getattr(self,x) for x in LOCATIONKEYS] + + def items(self): + return [(x,getattr(self,x)) for x in LOCATIONKEYS] + +def system(**kwargs:dict) -> dict: """Get/set system location settings Arguments: @@ -141,9 +177,9 @@ def system(**kwargs) -> dict: for x,y in save.items(): setter = "set" if x in data else "define" print(f'#{setter} {x}="{y}"',file=fh) - return result + return Location(**result) -def find(*address) -> dict: +def find(*address:list[str]) -> dict: """Find location data Arguments: @@ -179,10 +215,21 @@ def find(*address) -> dict: else: raise LocationError("no location found") - return result + return [Location(**x) for x in result] + +def set_location(file:str,**kwargs) -> dict: + """Set location in file + + Arguments: + + * `file`: file in which to set location data + + * `**kwargs`: location data + + Returns: -def set_location(file,**kwargs): - """TODO""" + * Previous values + """ data = json.load(open(file,"r")) result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in LOCATIONKEYS} for key,value in kwargs.items(): @@ -194,13 +241,22 @@ def set_location(file,**kwargs): "value" : value, } json.dump(data,open(file,"w"),indent=4) - return result + return Location(**result) + +def get_location(file:str) -> dict: + """Get location data in file + + Arguments: -def get_location(file): - """TODO""" + * `file`: file from which to get location data + + Returns: + + * Current values + """ data = json.load(open(file,"r")) result = {x:(data["globals"][x]["value"] if x in data["globals"] else "") for x in LOCATIONKEYS} - return result + return Location(**result) def main(argv:list) -> int: """Main location routine @@ -225,14 +281,23 @@ def output_raw(data,**kwargs): def output_csv(data,**kwargs): if isinstance(data,list): - print("\n".join(data)) - elif isinstance(data,dict): - print("\n".join([f"{x},{y}" for x,y in data.items()])) + print(",".join(LOCATIONKEYS)) + for item in data: + print(",".join([str(item[x]) for x in LOCATIONKEYS])) + elif isinstance(data,Location): + print(",".join(LOCATIONKEYS)) + print(",".join([str(data[x]) for x in LOCATIONKEYS])) else: - raise ResourceError(f"unable to output '{type(data)}' as CSV") + raise LocationError(f"unable to output '{type(data)}' as CSV") def output_json(data,**kwargs): - print(json.dumps(data,**kwargs)) + if isinstance(data,list): + print(json.dumps([dict(x.items()) for x in data],**kwargs)) + elif isinstance(data,Location): + print(json.dumps(dict(data.items()),**kwargs)) + else: + raise LocationError(f"unable to output '{type(data)}' as CSV") + outputter = output_raw outputter_options = {} @@ -310,13 +375,21 @@ def output_json(data,**kwargs): # TODO: development testing -- only needed when developing/debugging # if not sys.argv[0]: - # # sys.argv = [__file__,"--system"] - # # sys.argv = [__file__,"--system=city:Menlo Park,state:CA,region:west,country:US"] - # # sys.argv = [__file__,"--find"] - # # sys.argv = [__file__,"--find=2575 Sand Hill Rd, Menlo Park, CA, USA"] - # # sys.argv = [__file__,"--find=2575 Sand Hill Rd, Menlo Park, CA","--find=7443 87th Dr NE, Marysville, WA"] - # # sys.argv = [__file__,"autotest/test_moutils.json"] - # # sys.argv = [__file__,"autotest/test_moutils.json=city:Seattle"] + # options = [] + # # options = ["--format=raw"] + # # options = ["--format=csv"] + # # options = ["--format=json,indent:4"] + # # options = ["--debug","--format=raw"] + # # options = ["--debug","--format=csv"] + # # options = ["--debug","--format=json,indent:4"] + + # # sys.argv = [__file__,*options,"--system"] + # # sys.argv = [__file__,*options,"--system=city:Menlo Park,state:CA,region:west,country:US"] + # # sys.argv = [__file__,*options,"--find"] + # # sys.argv = [__file__,*options,"--find=2575 Sand Hill Rd, Menlo Park, CA, USA"] + # # sys.argv = [__file__,*options,"--find=2575 Sand Hill Rd, Menlo Park, CA","--find=7443 87th Dr NE, Marysville, WA"] + # # sys.argv = [__file__,*options,"autotest/test_moutils.json"] + # # sys.argv = [__file__,*options,"autotest/test_moutils.json=city:Seattle"] rc = main(sys.argv) exit(rc)