From 89ff540f6450169e1d3b352bda3dbfa4730dd284 Mon Sep 17 00:00:00 2001 From: Corey White Date: Wed, 8 Jun 2022 18:09:06 -0400 Subject: [PATCH 01/19] Moved mapset logic from location class to mapset class and began implementing additional mapset requests --- actinia/location.py | 38 ++++----- actinia/mapset.py | 189 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 200 insertions(+), 27 deletions(-) diff --git a/actinia/location.py b/actinia/location.py index ea739e0..5fb0a89 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -77,17 +77,8 @@ def __request_mapsets(self): :return: A list of the mapset names """ - url = f"{self.__actinia.url}/locations/{self.name}/mapsets" - resp = requests.get(url, auth=self.__auth) - if resp.status_code != 200: - raise Exception(f"Error {resp.status_code}: {resp.text}") - - mapset_names = json.loads(resp.text)["process_results"] - mapsets = { - mname: Mapset(mname, self.__actinia, self.__auth) - for mname in mapset_names - } - self.mapsets = mapsets + self.mapsets = Mapset.list_mapsets_request(self.name, self.__actinia) + return self.mapsets def delete(self): """Delete a location via delete request. @@ -106,13 +97,24 @@ def get_mapsets(self): self.__request_mapsets() return self.mapsets - # def create_mapset(self, name, epsgcode): - # """ - # Creates a location with a specified projection. - # """ - # url = f"{self.url}/locations/{name}" - # # TODO - # # resp = requests.post(url, auth=(self.__auth)) + def create_mapset(self, name): + """ + Creates a mapset with in the location. + """ + mapset = Mapset.create_mapset_request(name, self.name, self.__actinia, self.__auth) + mapset.location = self + return mapset + + + def delete_mapset(self, name): + """ + Deletes a mapset and return update mapseet list for the location. + """ + Mapset.delete_mapset_request(name, self.name, self.__actinia, self.__auth) + # Refresh location mapsets from the server to make sure data isn't stale + self.__request_mapsets() + return self.mapsets + def __validate_process_chain(self, pc, type): url = f"{self.__actinia.url}/locations/{self.name}/" diff --git a/actinia/mapset.py b/actinia/mapset.py index 98eb8ce..e2db2ad 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -23,31 +23,202 @@ ####### __license__ = "GPLv3" -__author__ = "Anika Weinmann" +__author__ = "Anika Weinmann and Corey White" __copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" __maintainer__ = "Anika Weinmann" -# import json -# import requests -# -# from actinia.region import Region +from inspect import _void +import json +import requests +from actinia.region import Region +from enum import Enum, unique +@unique +class MAPSET_TASK(Enum): + INFO = "info" + LOCK = "lock" + RASTER_LAYER = "raster_layer" + STRDS = "strds" + VECTOR_LAYERS = "vector_layers" + PROCESSING = "processing" + PROCESSING_ASYNC = "processing_async" class Mapset: - def __init__(self, name, actinia, auth): + def __init__(self, name, location_name, actinia): self.name = name self.projection = None self.region = None + self.__location_name = location_name self.__actinia = actinia - self.__auth = auth self.raster_layers = None self.vector_layers = None self.strds = None + def __request_url( + actinia_url, + location_name, + mapset_name, + task = None + ): + """ + Provides the url to an Actinia mapset resource. + + Parameters + ---------- + actinia_url : str + The base url to actinia server + location_name : str + The GRASS location name + route: /locations/{location_name}/mapsets + mapset_name : str + The mapset name + route: /locations/{location_name}/mapsets/{mapset_name} + task : Enum(MAPSET_TASK) + The requested task + (info) route: + /locations/{location_name}/mapsets/{mapset_name}/info - GET + (lock) route: + /locations/{location_name}/mapsets/{mapset_name}/raster_layers - DELETE, GET, PUT + (raster_layers) route: + /locations/{location_name}/mapsets/{mapset_name}/raster_layers - DELETE, GET, PUT + (vector_layers) route: + /locations/{location_name}/mapsets/{mapset_name}/vector_layers - DELETE, GET, PUT + (strds) route: + /locations/{location_name}/mapsets/{mapset_name}/strds - GET + (processing) route: + /locations/{location_name}/mapsets/{mapset_name}/processing - POST (persistent, asyncron) + (processing_async) route: + /locations/{location_name}/mapsets/{mapset_name}/processing_async - POST (persistent, asyncron) + + raster_layers : bool + Returns + ------- + base_url : str + Return the url scheme for the mapset request + """ + base_url = f"{actinia_url}/locations/{location_name}/mapsets" + if mapset_name is not None: + base_url = f"{base_url}/{mapset_name}" + if isinstance(task, MAPSET_TASK): + base_url = f"{base_url}/{mapset_name}{task.value}" + + return base_url + + def info(self): + """Get mapset info""" + if self.projection is None or self.region is None: + proc_res = self.request_info(self.name, self.__location_name, self.__actinia) + self.projection = proc_res["projection"] + self.region = Region(**proc_res["region"]) + return {"projection": self.projection, "region": self.region} + + + def delete(self): + """Deletes the mapset""" + self.delete_mapset_request(self.name, self.__location_name, self.__actinia) + + + @classmethod + def list_mapsets_request(cls, location_name, actinia): + """ + Creates a mapset within a location. + + Parameters + ---------- + mapset_name : str + The name of the created mapset. + location_name : str + The name of the location where the mapset is created + actinia : Actinia + An Actinia instance containing the url and authentication details + + Returns + ------- + Mapset + A new mapset instance for the created mapset + """ + url = cls.__request_url(actinia.url, location_name) + resp = requests.get(url, auth=actinia.auth) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + + mapset_names = json.loads(resp.text)["process_results"] + mapsets = { + mname: Mapset(mname, actinia) + for mname in mapset_names + } + return mapsets + + + @classmethod + def create_mapset_request(cls, mapset_name, location_name, actinia): + """ + Creates a mapset within a location. + + Parameters + ---------- + mapset_name : str + The name of the created mapset. + location_name : str + The name of the location where the mapset is created + actinia : Actinia + An Actinia instance containing the url and authentication details + + Returns + ------- + Mapset + A new mapset instance for the created mapset + """ + url = cls.__request_url(actinia.url, location_name, mapset_name) + resp = requests.post(url, auth=(actinia.auth)) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + return Mapset(mapset_name, actinia, actinia.auth) + + + @classmethod + def delete_mapset_request(cls, mapset_name, location_name, actinia): + """ + Delete a mapset within a location. + + Parameters + ---------- + mapset_name : str + The name of the created mapset. + location_name : str + The name of the location where the mapset is created + actinia : Actinia + An Actinia instance containing the url and authentication details + """ + url = cls.__request_url(actinia.url, location_name, mapset_name) + resp = requests.delete(url, auth=(actinia.auth)) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + return None + + + @classmethod + def request_info(cls, mapset_name, location_name, actinia): + """ + Delete a mapset within a location. + + Parameters + ---------- + mapset_name : str + The name of the created mapset. + location_name : str + The name of the location where the mapset is created + actinia : Actinia + An Actinia instance containing the url and authentication details + """ + url = cls.__request_url(actinia.url, location_name, mapset_name, MAPSET_TASK.INFO) + resp = requests.get(url, auth=(actinia.auth)) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + proc_res = json.loads(resp.text)["process_results"] + return proc_res # TODO: -# * /locations/{location_name}/mapsets/{mapset_name} - DELETE, POST -# * /locations/{location_name}/mapsets/{mapset_name}/info - GET # * (/locations/{location_name}/mapsets/{mapset_name}/lock - GET, DELETE, POST) # * /locations/{location_name}/mapsets/{mapset_name}/raster_layers From d2580738648e04bab575361620b7feca6dbe8881 Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 9 Jun 2022 02:59:03 -0400 Subject: [PATCH 02/19] Started testing new mapset fuctionality and added mock response data --- actinia/location.py | 2 +- actinia/mapset.py | 41 +-- tests/mock/actinia_mapset_management_mock.py | 258 +++++++++++++++++++ tests/mock/actinia_mock.py | 2 +- tests/test_actinia_mapset_management.py | 22 ++ 5 files changed, 306 insertions(+), 19 deletions(-) create mode 100644 tests/mock/actinia_mapset_management_mock.py diff --git a/actinia/location.py b/actinia/location.py index 5fb0a89..28faeb8 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -77,7 +77,7 @@ def __request_mapsets(self): :return: A list of the mapset names """ - self.mapsets = Mapset.list_mapsets_request(self.name, self.__actinia) + self.mapsets = Mapset.list_mapsets_request(self.name, self.__actinia, self.__auth) return self.mapsets def delete(self): diff --git a/actinia/mapset.py b/actinia/mapset.py index e2db2ad..a4c91bc 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -44,12 +44,13 @@ class MAPSET_TASK(Enum): PROCESSING_ASYNC = "processing_async" class Mapset: - def __init__(self, name, location_name, actinia): + def __init__(self, name, location_name, actinia, auth): self.name = name self.projection = None self.region = None self.__location_name = location_name self.__actinia = actinia + self.__auth = auth self.raster_layers = None self.vector_layers = None self.strds = None @@ -57,7 +58,7 @@ def __init__(self, name, location_name, actinia): def __request_url( actinia_url, location_name, - mapset_name, + mapset_name = None, task = None ): """ @@ -70,10 +71,10 @@ def __request_url( location_name : str The GRASS location name route: /locations/{location_name}/mapsets - mapset_name : str + mapset_name : str, default=None The mapset name route: /locations/{location_name}/mapsets/{mapset_name} - task : Enum(MAPSET_TASK) + task : Enum(MAPSET_TASK), default=None The requested task (info) route: /locations/{location_name}/mapsets/{mapset_name}/info - GET @@ -107,7 +108,7 @@ def __request_url( def info(self): """Get mapset info""" if self.projection is None or self.region is None: - proc_res = self.request_info(self.name, self.__location_name, self.__actinia) + proc_res = self.request_info(self.name, self.__location_name, self.__actinia, self.__auth) self.projection = proc_res["projection"] self.region = Region(**proc_res["region"]) return {"projection": self.projection, "region": self.region} @@ -119,7 +120,7 @@ def delete(self): @classmethod - def list_mapsets_request(cls, location_name, actinia): + def list_mapsets_request(cls, location_name, actinia, auth): """ Creates a mapset within a location. @@ -131,27 +132,28 @@ def list_mapsets_request(cls, location_name, actinia): The name of the location where the mapset is created actinia : Actinia An Actinia instance containing the url and authentication details - + auth : + Actinia authentication Returns ------- Mapset A new mapset instance for the created mapset """ url = cls.__request_url(actinia.url, location_name) - resp = requests.get(url, auth=actinia.auth) + resp = requests.get(url, auth=auth) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") mapset_names = json.loads(resp.text)["process_results"] mapsets = { - mname: Mapset(mname, actinia) + mname: Mapset(mname, location_name, actinia, auth) for mname in mapset_names } return mapsets @classmethod - def create_mapset_request(cls, mapset_name, location_name, actinia): + def create_mapset_request(cls, mapset_name, location_name, actinia, auth): """ Creates a mapset within a location. @@ -163,21 +165,22 @@ def create_mapset_request(cls, mapset_name, location_name, actinia): The name of the location where the mapset is created actinia : Actinia An Actinia instance containing the url and authentication details - + auth : + Actinia authentication Returns ------- Mapset A new mapset instance for the created mapset """ url = cls.__request_url(actinia.url, location_name, mapset_name) - resp = requests.post(url, auth=(actinia.auth)) + resp = requests.post(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") - return Mapset(mapset_name, actinia, actinia.auth) + return Mapset(mapset_name, actinia, auth) @classmethod - def delete_mapset_request(cls, mapset_name, location_name, actinia): + def delete_mapset_request(cls, mapset_name, location_name, actinia, auth): """ Delete a mapset within a location. @@ -189,16 +192,18 @@ def delete_mapset_request(cls, mapset_name, location_name, actinia): The name of the location where the mapset is created actinia : Actinia An Actinia instance containing the url and authentication details + auth : + Actinia authentication """ url = cls.__request_url(actinia.url, location_name, mapset_name) - resp = requests.delete(url, auth=(actinia.auth)) + resp = requests.delete(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") return None @classmethod - def request_info(cls, mapset_name, location_name, actinia): + def request_info(cls, mapset_name, location_name, actinia, auth): """ Delete a mapset within a location. @@ -210,9 +215,11 @@ def request_info(cls, mapset_name, location_name, actinia): The name of the location where the mapset is created actinia : Actinia An Actinia instance containing the url and authentication details + auth : + Actinia authentication """ url = cls.__request_url(actinia.url, location_name, mapset_name, MAPSET_TASK.INFO) - resp = requests.get(url, auth=(actinia.auth)) + resp = requests.get(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") proc_res = json.loads(resp.text)["process_results"] diff --git a/tests/mock/actinia_mapset_management_mock.py b/tests/mock/actinia_mapset_management_mock.py new file mode 100644 index 0000000..e0c24d5 --- /dev/null +++ b/tests/mock/actinia_mapset_management_mock.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# acintia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "Corey White" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "Anika Weinmann" + +from .actinia_mock import ( + ACTINIA_BASEURL, + ACTINIA_VERSION, + fill_basic_frame, +) + +mapset_creation_resp = { + "accept_datetime": "2022-05-27 07:44:19.428476", + "accept_timestamp": 1653637459.4284744, + "api_info": { + "endpoint": "mapsetmanagementresourceadmin", + "method": "POST", + "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "test_location/mapsets/test_mapset" + }, + "datetime": "2022-05-27 07:44:20.738901", + "http_code": 200, + "message": "Mapset successfully created.", + "process_chain_list": [{ + "1": { + "flags": "l", + "module": "g.mapsets" + } + }], + "process_log": [{ + "executable": "g.mapsets", + "id": "1", + "parameter": ["-l"], + "return_code": 0, + "run_time": 0.05029106140136719, + "stderr": ["Available mapsets:", ""], + "stdout": "PERMANENT test_mapset\n" + }], + "process_results": {}, + "progress": { + "num_of_steps": 1, + "step": 1 + }, + "resource_id": "resource_id-b4a57ebe-b3e9-4e6a-bab6-c01e7b97f670", + "status": "finished", + "time_delta": 1.310459852218628, + "timestamp": 1653637460.738866, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-b4a57ebe-b3e9-4e6a-bab6-c01e7b97f670" + }, + "user_id": "testuser" +} + +delete_mapset_resp = { + "response": { + "accept_datetime": "2022-06-09 00:04:15.807052", + "accept_timestamp": 1654733055.807051, + "api_info": { + "endpoint": "mapsetmanagementresourceadmin", + "method": "DELETE", + "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset" + }, + "datetime": "2022-06-09 00:04:15.866212", + "http_code": 200, + "message": "Mapset successfully removed.", + "process_chain_list": [], + "process_log": [], + "process_results": {}, + "progress": { + "num_of_steps": 0, + "step": 0 + }, + "resource_id": "resource_id-af7aa2aa-5db0-4e8b-b921-05a7b99ac0c5", + "status": "finished", + "time_delta": 0.059175729751586914, + "timestamp": 1654733055.8661997, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser/resource_id-af7aa2aa-5db0-4e8b-b921-05a7b99ac0c5" + }, + "user_id": "testuser" + } +} + +get_mapsets_resp = { + "response": { + "accept_datetime": "2022-06-08 23:47:58.962452", + "accept_timestamp": 1654732078.9624505, + "api_info": { + "endpoint": "listmapsetsresource", + "method": "GET", + "path": "/api/v3/locations/test_location/mapsets", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/test_location/mapsets" + }, + "datetime": "2022-06-08 23:47:59.118004", + "http_code": 200, + "message": "Processing successfully finished", + "process_chain_list": [ + { + "1": { + "flags": "l", + "inputs": { + "separator": "newline" + }, + "module": "g.mapsets" + } + } + ], + "process_log": [ + { + "executable": "g.mapsets", + "id": "1", + "parameter": [ + "separator=newline", + "-l" + ], + "return_code": 0, + "run_time": 0.05015826225280762, + "stderr": ["Available mapsets:", ""], + "stdout": "test_mapset\n" + } + ], + "process_results": ["PERMANENT", "test_mapset"], + "progress": { + "num_of_steps": 1, "step": 1 + }, + "resource_id": "resource_id-ba4de6ee-733c-4c29-887d-2d6c14dbd17c", + "status": "finished", + "time_delta": 0.15557193756103516, + "timestamp": 1654732079.1179929, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser/resource_id-ba4de6ee-733c-4c29-887d-2d6c14dbd17c" + }, + "user_id": "testuser" + } + } + + +mapset_get_info_resp = { + "accept_datetime": "2022-06-09 06:12:43.495084", + "accept_timestamp": 1654755163.4950836, + "api_info": { + "endpoint": "mapsetmanagementresourceuser", + "method": "GET", + "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset/info/", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset/info/" + }, + "datetime": "2022-06-09 06:12:43.698443", + "http_code": 200, + "message": "Processing successfully finished", + "process_chain_list": [ + { + "1": { + "flags": "ug3", + "module": "g.region" + }, + "2": { + "flags": "fw", + "module": "g.proj" + } + } + ], + "process_log": [ + { + "executable": "g.region", + "id": "1", + "parameter": [ + "-ug3" + ], + "return_code": 0, + "run_time": 0.05020761489868164, + "stderr": [ + "" + ], + "stdout": "projection=99\nzone=0\nn=1\ns=0\nw=0\ne=1\nt=1\nb=0\nnsres=1\nnsres3=1\newres=1\newres3=1\ntbres=1\nrows=1\nrows3=1\ncols=1\ncols3=1\ndepths=1\ncells=1\ncells3=1\n" + }, + { + "executable": "g.proj", + "id": "2", + "parameter": [ + "-fw" + ], + "return_code": 0, + "run_time": 0.10024547576904297, + "stderr": [ + "" + ], + "stdout": "PROJCRS[\"NAD83(HARN) / North Carolina\",BASEGEOGCRS[\"NAD83(HARN)\",DATUM[\"NAD83 (High Accuracy Reference Network)\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4152]],CONVERSION[\"SPCS83 North Carolina zone (meters)\",METHOD[\"Lambert Conic Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of false origin\",33.75,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-79,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]],PARAMETER[\"Latitude of 1st standard parallel\",36.1666666666667,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard parallel\",34.3333333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting at false origin\",609601.22,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false origin\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United States (USA) - North Carolina - counties of Alamance; Alexander; Alleghany; Anson; Ashe; Avery; Beaufort; Bertie; Bladen; Brunswick; Buncombe; Burke; Cabarrus; Caldwell; Camden; Carteret; Caswell; Catawba; Chatham; Cherokee; Chowan; Clay; Cleveland; Columbus; Craven; Cumberland; Currituck; Dare; Davidson; Davie; Duplin; Durham; Edgecombe; Forsyth; Franklin; Gaston; Gates; Graham; Granville; Greene; Guilford; Halifax; Harnett; Haywood; Henderson; Hertford; Hoke; Hyde; Iredell; Jackson; Johnston; Jones; Lee; Lenoir; Lincoln; Macon; Madison; Martin; McDowell; Mecklenburg; Mitchell; Montgomery; Moore; Nash; New Hanover; Northampton; Onslow; Orange; Pamlico; Pasquotank; Pender; Perquimans; Person; Pitt; Polk; Randolph; Richmond; Robeson; Rockingham; Rowan; Rutherford; Sampson; Scotland; Stanly; Stokes; Surry; Swain; Transylvania; Tyrrell; Union; Vance; Wake; Warren; Washington; Watauga; Wayne; Wilkes; Wilson; Yadkin; Yancey.\"],BBOX[33.83,-84.33,36.59,-75.38]],ID[\"EPSG\",3358]]\n" + } + ], + "process_results": { + "projection": "PROJCRS[\"NAD83(HARN) / North Carolina\",BASEGEOGCRS[\"NAD83(HARN)\",DATUM[\"NAD83 (High Accuracy Reference Network)\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4152]],CONVERSION[\"SPCS83 North Carolina zone (meters)\",METHOD[\"Lambert Conic Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of false origin\",33.75,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-79,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]],PARAMETER[\"Latitude of 1st standard parallel\",36.1666666666667,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard parallel\",34.3333333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting at false origin\",609601.22,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false origin\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United States (USA) - North Carolina - counties of Alamance; Alexander; Alleghany; Anson; Ashe; Avery; Beaufort; Bertie; Bladen; Brunswick; Buncombe; Burke; Cabarrus; Caldwell; Camden; Carteret; Caswell; Catawba; Chatham; Cherokee; Chowan; Clay; Cleveland; Columbus; Craven; Cumberland; Currituck; Dare; Davidson; Davie; Duplin; Durham; Edgecombe; Forsyth; Franklin; Gaston; Gates; Graham; Granville; Greene; Guilford; Halifax; Harnett; Haywood; Henderson; Hertford; Hoke; Hyde; Iredell; Jackson; Johnston; Jones; Lee; Lenoir; Lincoln; Macon; Madison; Martin; McDowell; Mecklenburg; Mitchell; Montgomery; Moore; Nash; New Hanover; Northampton; Onslow; Orange; Pamlico; Pasquotank; Pender; Perquimans; Person; Pitt; Polk; Randolph; Richmond; Robeson; Rockingham; Rowan; Rutherford; Sampson; Scotland; Stanly; Stokes; Surry; Swain; Transylvania; Tyrrell; Union; Vance; Wake; Warren; Washington; Watauga; Wayne; Wilkes; Wilson; Yadkin; Yancey.\"],BBOX[33.83,-84.33,36.59,-75.38]],ID[\"EPSG\",3358]]\n", + "region": { + "b": 0, + "cells": 1, + "cells3": 1, + "cols": 1, + "cols3": 1, + "depths": 1, + "e": 1, + "ewres": 1, + "ewres3": 1, + "n": 1, + "nsres": 1, + "nsres3": 1, + "projection": 99, + "rows": 1, + "rows3": 1, + "s": 0, + "t": 1, + "tbres": 1, + "w": 0, + "zone": 0 + } + }, + "progress": { + "num_of_steps": 2, + "step": 2 + }, + "resource_id": "resource_id-a5b61779-ee68-426b-b3ed-991aaf24b980", + "status": "finished", + "time_delta": 0.20337486267089844, + "timestamp": 1654755163.698429, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser/resource_id-a5b61779-ee68-426b-b3ed-991aaf24b980" + }, + "user_id": "testuser" +} diff --git a/tests/mock/actinia_mock.py b/tests/mock/actinia_mock.py index 3427a80..b7e7520 100644 --- a/tests/mock/actinia_mock.py +++ b/tests/mock/actinia_mock.py @@ -147,7 +147,7 @@ def fill_basic_frame( # location_get_info_resp = fill_basic_frame(p_result_location_info) -p_result_get_mapsets = ["PERMANENT", "True", "landsat", "modis_lst"] +p_result_get_mapsets = ["PERMANENT", "True", "landsat", "modis_lst", "test_mapset"] location_get_mapset_resp = fill_basic_frame(p_result_get_mapsets) start_job_resp = fill_basic_frame(process_results={}, status="accepted") diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index e570128..06f9224 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -32,12 +32,14 @@ from actinia import Actinia from actinia.mapset import Mapset +from actinia.region import Region from .mock.actinia_mock import ( ACTINIA_BASEURL, version_resp_text, location_get_mapset_resp, ) +from .mock.actinia_mapset_management_mock import mapset_get_info_resp from .mock.actinia_location_management_mock import get_locations_resp __license__ = "GPLv3" @@ -45,6 +47,7 @@ __copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" LOCATION_NAME = "nc_spm_08" +MAPSET_NAME = "test_mapset" PC = { "list": [ { @@ -95,3 +98,22 @@ def test_location_get_mapsets(self): resp["PERMANENT"], Mapset ), "Mapsets not of type Mapset" assert resp == self.testactinia.locations[LOCATION_NAME].mapsets + + def test_mapset_get_info(self): + """Test mapset get_info method.""" + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(mapset_get_info_resp) + resp = self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].info() + + assert "region" in resp, "'region' not in location info" + assert "projection" in resp, "'projection' not in location info" + assert isinstance(resp["projection"], str), "'projection' wrong type" + assert isinstance(resp["region"], Region), "'region' wrong type" + region = resp["region"] + assert hasattr(region, "n"), "Region has not the attribute 'n'" + assert region == self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].region + assert ( + resp["projection"] + == self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].projection + ) \ No newline at end of file From 4c18feb57acc14e0a5878ac0eb99cf3f6e3a60ce Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 10 Jun 2022 00:52:32 -0400 Subject: [PATCH 03/19] Added mapset create and delete tests --- actinia/location.py | 5 ++- actinia/mapset.py | 2 +- tests/test_actinia_mapset_management.py | 58 +++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/actinia/location.py b/actinia/location.py index 28faeb8..ad8b734 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -102,7 +102,10 @@ def create_mapset(self, name): Creates a mapset with in the location. """ mapset = Mapset.create_mapset_request(name, self.name, self.__actinia, self.__auth) - mapset.location = self + mapset.location = self.name + # We could also fetch data from the server again + # with self.__request_mapsets() to ensure data is stale + self.mapsets[name] = mapset return mapset diff --git a/actinia/mapset.py b/actinia/mapset.py index a4c91bc..f11936e 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -176,7 +176,7 @@ def create_mapset_request(cls, mapset_name, location_name, actinia, auth): resp = requests.post(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") - return Mapset(mapset_name, actinia, auth) + return Mapset(mapset_name, location_name, actinia, auth) @classmethod diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index 06f9224..ebf1c83 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -39,15 +39,27 @@ version_resp_text, location_get_mapset_resp, ) -from .mock.actinia_mapset_management_mock import mapset_get_info_resp + from .mock.actinia_location_management_mock import get_locations_resp +from .mock.actinia_mapset_management_mock import ( + mapset_get_info_resp, + mapset_creation_resp, + delete_mapset_resp +) + + + __license__ = "GPLv3" __author__ = "Anika Weinmann" __copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +LOCATION_NAME = "nc_spm_08" +NEW_LOCATION_NAME = "test_location" +EPSGCODE = 25832 LOCATION_NAME = "nc_spm_08" MAPSET_NAME = "test_mapset" +NEW_MAPSET_NAME = "new_test_mapset" PC = { "list": [ { @@ -64,9 +76,11 @@ class TestActiniaLocation(object): @classmethod def setup_class(cls): cls.mock_get_patcher = patch("actinia.actinia.requests.get") - cls.mock_post_patcher = patch("actinia.actinia.requests.post") cls.mock_get = cls.mock_get_patcher.start() + cls.mock_post_patcher = patch("actinia.actinia.requests.post") cls.mock_post = cls.mock_post_patcher.start() + cls.mock_delete_patcher = patch("actinia.actinia.requests.delete") + cls.mock_delete = cls.mock_delete_patcher.start() cls.mock_get.return_value = Mock() cls.mock_get.return_value.status_code = 200 @@ -84,6 +98,16 @@ def setup_class(cls): def teardown_class(cls): cls.mock_get_patcher.stop() cls.mock_post_patcher.stop() + cls.mock_delete_patcher.stop() + if NEW_LOCATION_NAME in cls.testactinia.locations: + cls.mock_delete_mapset() + cls.testactinia.locations[NEW_LOCATION_NAME].delete_mapset(NEW_MAPSET_NAME) + + @classmethod + def mock_delete_mapset(cls): + cls.mock_delete.return_value = Mock() + cls.mock_delete.return_value.status_code = 200 + cls.mock_delete.return_value.text = json.dumps(delete_mapset_resp) def test_location_get_mapsets(self): """Test location get_mapsets method.""" @@ -116,4 +140,32 @@ def test_mapset_get_info(self): assert ( resp["projection"] == self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].projection - ) \ No newline at end of file + ) + + + def test_actinia_create_and_delete_mapsets(self): + """Test location creation and deletion""" + self.mock_post.return_value = Mock() + self.mock_post.return_value.status_code = 200 + self.mock_post.return_value.text = json.dumps(mapset_creation_resp) + + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(location_get_mapset_resp) + + # create mapset + mapset = self.testactinia.locations[LOCATION_NAME].create_mapset( + NEW_MAPSET_NAME + ) + assert isinstance(mapset, Mapset), \ + "Created mapset is not of type Mapset" + assert mapset.name == NEW_MAPSET_NAME, \ + "Created location name is wrong" + assert NEW_MAPSET_NAME in self.testactinia.locations[LOCATION_NAME].mapsets, \ + "Created mapset is not added to location's mapsets" + + #Delete mapset + self.mock_delete_mapset() + self.testactinia.locations[LOCATION_NAME].delete_mapset(NEW_MAPSET_NAME) + assert NEW_MAPSET_NAME not in self.testactinia.locations[LOCATION_NAME].mapsets, \ + "Mapset not deleted" \ No newline at end of file From da0de8adc60b429058a1b212d90e7f4ed680ec95 Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 10 Jun 2022 10:13:41 -0400 Subject: [PATCH 04/19] Updated documentation --- actinia/mapset.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/actinia/mapset.py b/actinia/mapset.py index f11936e..b568a61 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -126,12 +126,10 @@ def list_mapsets_request(cls, location_name, actinia, auth): Parameters ---------- - mapset_name : str - The name of the created mapset. location_name : str - The name of the location where the mapset is created + The name of the location where the mapsets are located. actinia : Actinia - An Actinia instance containing the url and authentication details + An Actinia instance containing the url auth : Actinia authentication Returns @@ -164,7 +162,7 @@ def create_mapset_request(cls, mapset_name, location_name, actinia, auth): location_name : str The name of the location where the mapset is created actinia : Actinia - An Actinia instance containing the url and authentication details + An Actinia instance containing the url auth : Actinia authentication Returns @@ -187,11 +185,11 @@ def delete_mapset_request(cls, mapset_name, location_name, actinia, auth): Parameters ---------- mapset_name : str - The name of the created mapset. + The name of the mapset to delete location_name : str - The name of the location where the mapset is created + The name of the mapset's location actinia : Actinia - An Actinia instance containing the url and authentication details + An Actinia instance containing the url auth : Actinia authentication """ @@ -210,11 +208,11 @@ def request_info(cls, mapset_name, location_name, actinia, auth): Parameters ---------- mapset_name : str - The name of the created mapset. + The name of mapset. location_name : str - The name of the location where the mapset is created + The name of the location actinia : Actinia - An Actinia instance containing the url and authentication details + An Actinia instance containing the url auth : Actinia authentication """ From 6d70fc847ecb4928e3905eb8362dd444bbf439c6 Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 10 Jun 2022 10:33:42 -0400 Subject: [PATCH 05/19] Fixed bug in delete mapset method added test --- actinia/mapset.py | 4 ++-- tests/test_actinia_mapset_management.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/actinia/mapset.py b/actinia/mapset.py index b568a61..d7f5e10 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -116,8 +116,8 @@ def info(self): def delete(self): """Deletes the mapset""" - self.delete_mapset_request(self.name, self.__location_name, self.__actinia) - + self.delete_mapset_request(self.name, self.__location_name, self.__actinia, self.__auth) + del self.__actinia.locations[self.__location_name].mapsets[self.name] @classmethod def list_mapsets_request(cls, location_name, actinia, auth): diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index ebf1c83..b0b820c 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -109,6 +109,12 @@ def mock_delete_mapset(cls): cls.mock_delete.return_value.status_code = 200 cls.mock_delete.return_value.text = json.dumps(delete_mapset_resp) + @classmethod + def mock_delete(cls): + cls.mock_delete.return_value = Mock() + cls.mock_delete.return_value.status_code = 200 + cls.mock_delete.return_value.text = json.dumps(delete_mapset_resp) + def test_location_get_mapsets(self): """Test location get_mapsets method.""" self.mock_get.return_value = Mock() @@ -164,8 +170,17 @@ def test_actinia_create_and_delete_mapsets(self): assert NEW_MAPSET_NAME in self.testactinia.locations[LOCATION_NAME].mapsets, \ "Created mapset is not added to location's mapsets" - #Delete mapset + #Delete mapset with Location method self.mock_delete_mapset() self.testactinia.locations[LOCATION_NAME].delete_mapset(NEW_MAPSET_NAME) + assert NEW_MAPSET_NAME not in self.testactinia.locations[LOCATION_NAME].mapsets, \ + "Mapset not deleted" + + # Recreate mapset and delete with Mapset method + mapset = self.testactinia.locations[LOCATION_NAME].create_mapset( + NEW_MAPSET_NAME + ) + self.mock_delete() + mapset.delete() assert NEW_MAPSET_NAME not in self.testactinia.locations[LOCATION_NAME].mapsets, \ "Mapset not deleted" \ No newline at end of file From b9f40925cabac6beec8265bee3ca7094aa08d8df Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 10 Jun 2022 10:40:24 -0400 Subject: [PATCH 06/19] Changed delete mapset to delete mapset from mapsets dict instead of refreshing form server. --- actinia/location.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actinia/location.py b/actinia/location.py index ad8b734..70b11be 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -114,8 +114,7 @@ def delete_mapset(self, name): Deletes a mapset and return update mapseet list for the location. """ Mapset.delete_mapset_request(name, self.name, self.__actinia, self.__auth) - # Refresh location mapsets from the server to make sure data isn't stale - self.__request_mapsets() + del self.mapsets[name] return self.mapsets From bca2b98d4b9bb0ef9c6051fc0c875280536cae78 Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 10 Jun 2022 18:21:38 -0400 Subject: [PATCH 07/19] Fixed flake8 errors --- .vscode/settings.json | 4 + actinia/location.py | 24 ++- actinia/mapset.py | 78 +++++----- tests/mock/actinia_mapset_management_mock.py | 147 +++++++++++++++---- tests/mock/actinia_mock.py | 8 +- tests/test_actinia_mapset_management.py | 41 ++++-- 6 files changed, 217 insertions(+), 85 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..491b1a1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.linting.enabled": true, + "python.linting.flake8Enabled": true +} \ No newline at end of file diff --git a/actinia/location.py b/actinia/location.py index 70b11be..7605c40 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -77,7 +77,11 @@ def __request_mapsets(self): :return: A list of the mapset names """ - self.mapsets = Mapset.list_mapsets_request(self.name, self.__actinia, self.__auth) + self.mapsets = Mapset.list_mapsets_request( + self.name, + self.__actinia, + self.__auth + ) return self.mapsets def delete(self): @@ -101,23 +105,31 @@ def create_mapset(self, name): """ Creates a mapset with in the location. """ - mapset = Mapset.create_mapset_request(name, self.name, self.__actinia, self.__auth) + mapset = Mapset.create_mapset_request( + name, + self.name, + self.__actinia, + self.__auth + ) mapset.location = self.name - # We could also fetch data from the server again + # We could also fetch data from the server again # with self.__request_mapsets() to ensure data is stale self.mapsets[name] = mapset return mapset - def delete_mapset(self, name): """ Deletes a mapset and return update mapseet list for the location. """ - Mapset.delete_mapset_request(name, self.name, self.__actinia, self.__auth) + Mapset.delete_mapset_request( + name, + self.name, + self.__actinia, + self.__auth + ) del self.mapsets[name] return self.mapsets - def __validate_process_chain(self, pc, type): url = f"{self.__actinia.url}/locations/{self.name}/" if type == "async": diff --git a/actinia/mapset.py b/actinia/mapset.py index d7f5e10..e08d9f7 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -27,12 +27,12 @@ __copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" __maintainer__ = "Anika Weinmann" -from inspect import _void import json import requests from actinia.region import Region from enum import Enum, unique + @unique class MAPSET_TASK(Enum): INFO = "info" @@ -43,6 +43,7 @@ class MAPSET_TASK(Enum): PROCESSING = "processing" PROCESSING_ASYNC = "processing_async" + class Mapset: def __init__(self, name, location_name, actinia, auth): self.name = name @@ -58,8 +59,8 @@ def __init__(self, name, location_name, actinia, auth): def __request_url( actinia_url, location_name, - mapset_name = None, - task = None + mapset_name=None, + task=None ): """ Provides the url to an Actinia mapset resource. @@ -75,29 +76,29 @@ def __request_url( The mapset name route: /locations/{location_name}/mapsets/{mapset_name} task : Enum(MAPSET_TASK), default=None - The requested task - (info) route: - /locations/{location_name}/mapsets/{mapset_name}/info - GET - (lock) route: - /locations/{location_name}/mapsets/{mapset_name}/raster_layers - DELETE, GET, PUT - (raster_layers) route: - /locations/{location_name}/mapsets/{mapset_name}/raster_layers - DELETE, GET, PUT - (vector_layers) route: - /locations/{location_name}/mapsets/{mapset_name}/vector_layers - DELETE, GET, PUT + The requested task + (info) route: + /locations/{location_name}/mapsets/{mapset_name}/info + (lock) route: + /locations/{location_name}/mapsets/{mapset_name}/raster_layers + (raster_layers) route: + /locations/{location_name}/mapsets/{mapset_name}/raster_layers + (vector_layers) route: + /locations/{location_name}/mapsets/{mapset_name}/vector_layers (strds) route: /locations/{location_name}/mapsets/{mapset_name}/strds - GET (processing) route: - /locations/{location_name}/mapsets/{mapset_name}/processing - POST (persistent, asyncron) + /locations/{location_name}/mapsets/{mapset_name}/processing (processing_async) route: - /locations/{location_name}/mapsets/{mapset_name}/processing_async - POST (persistent, asyncron) + /locations/{location_name}/mapsets/{mapset_name}/processing_async raster_layers : bool Returns ------- - base_url : str + base_url : str Return the url scheme for the mapset request """ - base_url = f"{actinia_url}/locations/{location_name}/mapsets" + base_url = f"{actinia_url}/locations/{location_name}/mapsets" if mapset_name is not None: base_url = f"{base_url}/{mapset_name}" if isinstance(task, MAPSET_TASK): @@ -108,18 +109,27 @@ def __request_url( def info(self): """Get mapset info""" if self.projection is None or self.region is None: - proc_res = self.request_info(self.name, self.__location_name, self.__actinia, self.__auth) + proc_res = self.request_info( + self.name, + self.__location_name, + self.__actinia, + self.__auth + ) self.projection = proc_res["projection"] self.region = Region(**proc_res["region"]) return {"projection": self.projection, "region": self.region} - def delete(self): """Deletes the mapset""" - self.delete_mapset_request(self.name, self.__location_name, self.__actinia, self.__auth) + self.delete_mapset_request( + self.name, + self.__location_name, + self.__actinia, + self.__auth + ) del self.__actinia.locations[self.__location_name].mapsets[self.name] - @classmethod + @classmethod def list_mapsets_request(cls, location_name, actinia, auth): """ Creates a mapset within a location. @@ -130,7 +140,7 @@ def list_mapsets_request(cls, location_name, actinia, auth): The name of the location where the mapsets are located. actinia : Actinia An Actinia instance containing the url - auth : + auth : Actinia authentication Returns ------- @@ -149,8 +159,7 @@ def list_mapsets_request(cls, location_name, actinia, auth): } return mapsets - - @classmethod + @classmethod def create_mapset_request(cls, mapset_name, location_name, actinia, auth): """ Creates a mapset within a location. @@ -163,21 +172,20 @@ def create_mapset_request(cls, mapset_name, location_name, actinia, auth): The name of the location where the mapset is created actinia : Actinia An Actinia instance containing the url - auth : + auth : Actinia authentication Returns ------- Mapset A new mapset instance for the created mapset """ - url = cls.__request_url(actinia.url, location_name, mapset_name) + url = cls.__request_url(actinia.url, location_name, mapset_name) resp = requests.post(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") return Mapset(mapset_name, location_name, actinia, auth) - - @classmethod + @classmethod def delete_mapset_request(cls, mapset_name, location_name, actinia, auth): """ Delete a mapset within a location. @@ -190,17 +198,16 @@ def delete_mapset_request(cls, mapset_name, location_name, actinia, auth): The name of the mapset's location actinia : Actinia An Actinia instance containing the url - auth : + auth : Actinia authentication """ - url = cls.__request_url(actinia.url, location_name, mapset_name) + url = cls.__request_url(actinia.url, location_name, mapset_name) resp = requests.delete(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") return None - - @classmethod + @classmethod def request_info(cls, mapset_name, location_name, actinia, auth): """ Delete a mapset within a location. @@ -213,10 +220,15 @@ def request_info(cls, mapset_name, location_name, actinia, auth): The name of the location actinia : Actinia An Actinia instance containing the url - auth : + auth : Actinia authentication """ - url = cls.__request_url(actinia.url, location_name, mapset_name, MAPSET_TASK.INFO) + url = cls.__request_url( + actinia.url, + location_name, + mapset_name, + MAPSET_TASK.INFO + ) resp = requests.get(url, auth=(auth)) if resp.status_code != 200: raise Exception(f"Error {resp.status_code}: {resp.text}") diff --git a/tests/mock/actinia_mapset_management_mock.py b/tests/mock/actinia_mapset_management_mock.py index e0c24d5..50138d8 100644 --- a/tests/mock/actinia_mapset_management_mock.py +++ b/tests/mock/actinia_mapset_management_mock.py @@ -29,8 +29,7 @@ from .actinia_mock import ( ACTINIA_BASEURL, - ACTINIA_VERSION, - fill_basic_frame, + ACTINIA_VERSION ) mapset_creation_resp = { @@ -39,7 +38,8 @@ "api_info": { "endpoint": "mapsetmanagementresourceadmin", "method": "POST", - "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset", + "path": f"/api/{ACTINIA_VERSION}/locations/test_location/" + + "mapsets/test_mapset", "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" "test_location/mapsets/test_mapset" }, @@ -85,8 +85,10 @@ "api_info": { "endpoint": "mapsetmanagementresourceadmin", "method": "DELETE", - "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset", - "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset" + "path": f"/api/{ACTINIA_VERSION}/locations/test_location/" + + "mapsets/test_mapset", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/" + + "locations/test_location/mapsets/test_mapset" }, "datetime": "2022-06-09 00:04:15.866212", "http_code": 200, @@ -104,7 +106,9 @@ "timestamp": 1654733055.8661997, "urls": { "resources": [], - "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser/resource_id-af7aa2aa-5db0-4e8b-b921-05a7b99ac0c5" + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/" + + "testuser" + + "/resource_id-af7aa2aa-5db0-4e8b-b921-05a7b99ac0c5" }, "user_id": "testuser" } @@ -118,23 +122,24 @@ "endpoint": "listmapsetsresource", "method": "GET", "path": "/api/v3/locations/test_location/mapsets", - "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/test_location/mapsets" - }, + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/" + + "locations/test_location/mapsets" + }, "datetime": "2022-06-08 23:47:59.118004", "http_code": 200, "message": "Processing successfully finished", "process_chain_list": [ { "1": { - "flags": "l", + "flags": "l", "inputs": { "separator": "newline" - }, + }, "module": "g.mapsets" } } - ], - "process_log": [ + ], + "process_log": [ { "executable": "g.mapsets", "id": "1", @@ -148,19 +153,20 @@ "stdout": "test_mapset\n" } ], - "process_results": ["PERMANENT", "test_mapset"], - "progress": { - "num_of_steps": 1, "step": 1 - }, - "resource_id": "resource_id-ba4de6ee-733c-4c29-887d-2d6c14dbd17c", - "status": "finished", - "time_delta": 0.15557193756103516, - "timestamp": 1654732079.1179929, - "urls": { - "resources": [], - "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser/resource_id-ba4de6ee-733c-4c29-887d-2d6c14dbd17c" - }, - "user_id": "testuser" + "process_results": ["PERMANENT", "test_mapset"], + "progress": { + "num_of_steps": 1, "step": 1 + }, + "resource_id": "resource_id-ba4de6ee-733c-4c29-887d-2d6c14dbd17c", + "status": "finished", + "time_delta": 0.15557193756103516, + "timestamp": 1654732079.1179929, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/" + + "testuser/resource_id-ba4de6ee-733c-4c29-887d-2d6c14dbd17c" + }, + "user_id": "testuser" } } @@ -171,8 +177,10 @@ "api_info": { "endpoint": "mapsetmanagementresourceuser", "method": "GET", - "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset/info/", - "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/test_location/mapsets/test_mapset/info/" + "path": f"/api/{ACTINIA_VERSION}/locations/test_location/mapsets/" + + "test_mapset/info/", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + + "test_location/mapsets/test_mapset/info/" }, "datetime": "2022-06-09 06:12:43.698443", "http_code": 200, @@ -201,7 +209,9 @@ "stderr": [ "" ], - "stdout": "projection=99\nzone=0\nn=1\ns=0\nw=0\ne=1\nt=1\nb=0\nnsres=1\nnsres3=1\newres=1\newres3=1\ntbres=1\nrows=1\nrows3=1\ncols=1\ncols3=1\ndepths=1\ncells=1\ncells3=1\n" + "stdout": "projection=99\nzone=0\nn=1\ns=0\nw=0\ne=1\nt=1\n" + + "b=0\nnsres=1\nnsres3=1\newres=1\newres3=1\ntbres=1\nrows=1\n" + + "rows3=1\ncols=1\ncols3=1\ndepths=1\ncells=1\ncells3=1\n" }, { "executable": "g.proj", @@ -214,11 +224,85 @@ "stderr": [ "" ], - "stdout": "PROJCRS[\"NAD83(HARN) / North Carolina\",BASEGEOGCRS[\"NAD83(HARN)\",DATUM[\"NAD83 (High Accuracy Reference Network)\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4152]],CONVERSION[\"SPCS83 North Carolina zone (meters)\",METHOD[\"Lambert Conic Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of false origin\",33.75,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-79,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]],PARAMETER[\"Latitude of 1st standard parallel\",36.1666666666667,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard parallel\",34.3333333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting at false origin\",609601.22,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false origin\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United States (USA) - North Carolina - counties of Alamance; Alexander; Alleghany; Anson; Ashe; Avery; Beaufort; Bertie; Bladen; Brunswick; Buncombe; Burke; Cabarrus; Caldwell; Camden; Carteret; Caswell; Catawba; Chatham; Cherokee; Chowan; Clay; Cleveland; Columbus; Craven; Cumberland; Currituck; Dare; Davidson; Davie; Duplin; Durham; Edgecombe; Forsyth; Franklin; Gaston; Gates; Graham; Granville; Greene; Guilford; Halifax; Harnett; Haywood; Henderson; Hertford; Hoke; Hyde; Iredell; Jackson; Johnston; Jones; Lee; Lenoir; Lincoln; Macon; Madison; Martin; McDowell; Mecklenburg; Mitchell; Montgomery; Moore; Nash; New Hanover; Northampton; Onslow; Orange; Pamlico; Pasquotank; Pender; Perquimans; Person; Pitt; Polk; Randolph; Richmond; Robeson; Rockingham; Rowan; Rutherford; Sampson; Scotland; Stanly; Stokes; Surry; Swain; Transylvania; Tyrrell; Union; Vance; Wake; Warren; Washington; Watauga; Wayne; Wilkes; Wilson; Yadkin; Yancey.\"],BBOX[33.83,-84.33,36.59,-75.38]],ID[\"EPSG\",3358]]\n" + "stdout": "PROJCRS[\"NAD83(HARN) / North Carolina\",BASEGEOGCRS" + + "[\"NAD83(HARN)\",DATUM[\"NAD83 (High Accuracy Reference" + + "Network)\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101," + + "LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT" + + "[\"degree\",0.0174532925199433]],ID[\"EPSG\",4152]],CONVERSION" + + "[\"SPCS83 North Carolina zone (meters)\",METHOD[\"Lambert Conic" + + "Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of" + + "false origin\",33.75,ANGLEUNIT[\"degree\",0.0174532925199433]" + + ",ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\"," + + "-79,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822" + + "]],PARAMETER[\"Latitude of 1st standard parallel\"," + + "36.1666666666667,ANGLEUNIT[\"degree\",0.0174532925199433]," + + "ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard " + + "parallel\",34.3333333333333,ANGLEUNIT[\"degree\"," + + "0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting " + + "at false origin\",609601.22,LENGTHUNIT[\"metre\",1],ID[\"EP" + + "SG\",8826]],PARAMETER[\"Northing at falseorigin\",0,LENGTH" + + "UNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2]," + + "AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]]" + + ",AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT" + + "[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic" + + "mapping.\"],AREA[\"United States (USA) - North Carolina -" + + "counties of Alamance; Alexander; Alleghany; Anson; " + + "Ashe; Avery; Beaufort; Bertie; Bladen; Brunswick; " + + "Buncombe; Burke; Cabarrus; Caldwell; Camden; " + + "Carteret; Caswell; Catawba; Chatham; Cherokee; Chowan; Clay; " + + "Cleveland; Columbus; Craven; Cumberland; Currituck; Dare; " + + "Davidson; Davie; Duplin; Durham; Edgecombe; Forsyth; Franklin; " + + "Gaston; Gates; Graham; Granville; Greene; Guilford; Halifax; " + + "Harnett; Haywood; Henderson; Hertford; Hoke; Hyde; Iredell; " + + "Jackson; Johnston; Jones; Lee; Lenoir; Lincoln; Macon; " + + "Madison; Martin; McDowell; Mecklenburg; Mitchell; Montgomery; " + + "Moore; Nash; New Hanover; Northampton; Onslow; Orange; " + + "Pamlico; Pasquotank; Pender; Perquimans; Person; Pitt; Polk; " + + "Randolph; Richmond; Robeson; Rockingham; Rowan; Rutherford; " + + "Sampson; Scotland; Stanly; Stokes; Surry; Swain; Transylvania; " + + "Tyrrell; Union; Vance; Wake; Warren; Washington; Watauga; " + + "Wayne; Wilkes; Wilson; Yadkin; Yancey.\"],BBOX[33.83,-84.33," + + "36.59,-75.38]],ID[\"EPSG\",3358]]\n", } ], "process_results": { - "projection": "PROJCRS[\"NAD83(HARN) / North Carolina\",BASEGEOGCRS[\"NAD83(HARN)\",DATUM[\"NAD83 (High Accuracy Reference Network)\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4152]],CONVERSION[\"SPCS83 North Carolina zone (meters)\",METHOD[\"Lambert Conic Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of false origin\",33.75,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-79,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]],PARAMETER[\"Latitude of 1st standard parallel\",36.1666666666667,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard parallel\",34.3333333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting at false origin\",609601.22,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false origin\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United States (USA) - North Carolina - counties of Alamance; Alexander; Alleghany; Anson; Ashe; Avery; Beaufort; Bertie; Bladen; Brunswick; Buncombe; Burke; Cabarrus; Caldwell; Camden; Carteret; Caswell; Catawba; Chatham; Cherokee; Chowan; Clay; Cleveland; Columbus; Craven; Cumberland; Currituck; Dare; Davidson; Davie; Duplin; Durham; Edgecombe; Forsyth; Franklin; Gaston; Gates; Graham; Granville; Greene; Guilford; Halifax; Harnett; Haywood; Henderson; Hertford; Hoke; Hyde; Iredell; Jackson; Johnston; Jones; Lee; Lenoir; Lincoln; Macon; Madison; Martin; McDowell; Mecklenburg; Mitchell; Montgomery; Moore; Nash; New Hanover; Northampton; Onslow; Orange; Pamlico; Pasquotank; Pender; Perquimans; Person; Pitt; Polk; Randolph; Richmond; Robeson; Rockingham; Rowan; Rutherford; Sampson; Scotland; Stanly; Stokes; Surry; Swain; Transylvania; Tyrrell; Union; Vance; Wake; Warren; Washington; Watauga; Wayne; Wilkes; Wilson; Yadkin; Yancey.\"],BBOX[33.83,-84.33,36.59,-75.38]],ID[\"EPSG\",3358]]\n", + "projection": "PROJCRS[\"NAD83(HARN) / North Carolina\",BASEGEOGCRS" + + "[\"NAD83(HARN)\",DATUM[\"NAD83 (High Accuracy Reference" + + "Network)\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101," + + "LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT" + + "[\"degree\",0.0174532925199433]],ID[\"EPSG\",4152]],CONVERSION" + + "[\"SPCS83 North Carolina zone (meters)\",METHOD[\"Lambert Conic" + + "Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of" + + "false origin\",33.75,ANGLEUNIT[\"degree\",0.0174532925199433],ID" + + "[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-79," + + "ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]]," + + "PARAMETER[\"Latitude of 1st standard parallel\",36.1666666666667," + + "ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]]," + + "PARAMETER[\"Latitude of 2nd standard parallel\",34.3333333333333" + + ",ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]]," + + "PARAMETER[\"Easting at false origin\",609601.22,LENGTHUNIT" + + "[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false" + + "origin\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[" + + "Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT" + + "[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT" + + "[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic" + + "mapping.\"],AREA[\"United States (USA) - North Carolina -" + + "counties of Alamance; Alexander; Alleghany; Anson; Ashe; Avery; " + + "Beaufort; Bertie; Bladen; Brunswick; Buncombe; Burke; Cabarrus; " + + "Caldwell; Camden; Carteret; Caswell; Catawba; Chatham; Cherokee; " + + "Chowan; Clay; Cleveland; Columbus; Craven; Cumberland; Currituck; " + + "Dare; Davidson; Davie; Duplin; Durham; Edgecombe; Forsyth; " + + "Franklin; Gaston; Gates; Graham; Granville; Greene; Guilford; " + + "Halifax; Harnett; Haywood; Henderson; Hertford; Hoke; Hyde; " + + "Iredell; Jackson; Johnston; Jones; Lee; Lenoir; Lincoln; Macon; " + + "Madison; Martin; McDowell; Mecklenburg; Mitchell; Montgomery; " + + "Moore; Nash; New Hanover; Northampton; Onslow; Orange; Pamlico; " + + "Pasquotank; Pender; Perquimans; Person; Pitt; Polk; Randolph; " + + "Richmond; Robeson; Rockingham; Rowan; Rutherford; Sampson; " + + "Scotland; Stanly; Stokes; Surry; Swain; Transylvania; Tyrrell; " + + "Union; Vance; Wake; Warren; Washington; Watauga; Wayne; Wilkes; " + + "Wilson; Yadkin; Yancey.\"],BBOX[33.83,-84.33,36.59,-75.38]]," + + "ID[\"EPSG\",3358]]\n", "region": { "b": 0, "cells": 1, @@ -252,7 +336,8 @@ "timestamp": 1654755163.698429, "urls": { "resources": [], - "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser/resource_id-a5b61779-ee68-426b-b3ed-991aaf24b980" + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/" + + "testuser/resource_id-a5b61779-ee68-426b-b3ed-991aaf24b980" }, "user_id": "testuser" } diff --git a/tests/mock/actinia_mock.py b/tests/mock/actinia_mock.py index b7e7520..6fe5482 100644 --- a/tests/mock/actinia_mock.py +++ b/tests/mock/actinia_mock.py @@ -147,7 +147,13 @@ def fill_basic_frame( # location_get_info_resp = fill_basic_frame(p_result_location_info) -p_result_get_mapsets = ["PERMANENT", "True", "landsat", "modis_lst", "test_mapset"] +p_result_get_mapsets = [ + "PERMANENT", + "True", + "landsat", + "modis_lst", + "test_mapset" +] location_get_mapset_resp = fill_basic_frame(p_result_get_mapsets) start_job_resp = fill_basic_frame(process_results={}, status="accepted") diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index b0b820c..f33ba55 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -48,8 +48,6 @@ delete_mapset_resp ) - - __license__ = "GPLv3" __author__ = "Anika Weinmann" __copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" @@ -101,8 +99,10 @@ def teardown_class(cls): cls.mock_delete_patcher.stop() if NEW_LOCATION_NAME in cls.testactinia.locations: cls.mock_delete_mapset() - cls.testactinia.locations[NEW_LOCATION_NAME].delete_mapset(NEW_MAPSET_NAME) - + cls.testactinia.locations[NEW_LOCATION_NAME].delete_mapset( + NEW_MAPSET_NAME + ) + @classmethod def mock_delete_mapset(cls): cls.mock_delete.return_value = Mock() @@ -134,7 +134,9 @@ def test_mapset_get_info(self): self.mock_get.return_value = Mock() self.mock_get.return_value.status_code = 200 self.mock_get.return_value.text = json.dumps(mapset_get_info_resp) - resp = self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].info() + resp = self.testactinia.locations[ + LOCATION_NAME + ].mapsets[MAPSET_NAME].info() assert "region" in resp, "'region' not in location info" assert "projection" in resp, "'projection' not in location info" @@ -142,13 +144,16 @@ def test_mapset_get_info(self): assert isinstance(resp["region"], Region), "'region' wrong type" region = resp["region"] assert hasattr(region, "n"), "Region has not the attribute 'n'" - assert region == self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].region + assert region == self.testactinia.locations[ + LOCATION_NAME + ].mapsets[MAPSET_NAME].region assert ( resp["projection"] - == self.testactinia.locations[LOCATION_NAME].mapsets[MAPSET_NAME].projection + == self.testactinia.locations[ + LOCATION_NAME + ].mapsets[MAPSET_NAME].projection ) - def test_actinia_create_and_delete_mapsets(self): """Test location creation and deletion""" self.mock_post.return_value = Mock() @@ -167,13 +172,19 @@ def test_actinia_create_and_delete_mapsets(self): "Created mapset is not of type Mapset" assert mapset.name == NEW_MAPSET_NAME, \ "Created location name is wrong" - assert NEW_MAPSET_NAME in self.testactinia.locations[LOCATION_NAME].mapsets, \ + assert NEW_MAPSET_NAME in self.testactinia.locations[ + LOCATION_NAME + ].mapsets, \ "Created mapset is not added to location's mapsets" - #Delete mapset with Location method + # Delete mapset with Location method self.mock_delete_mapset() - self.testactinia.locations[LOCATION_NAME].delete_mapset(NEW_MAPSET_NAME) - assert NEW_MAPSET_NAME not in self.testactinia.locations[LOCATION_NAME].mapsets, \ + self.testactinia.locations[LOCATION_NAME].delete_mapset( + NEW_MAPSET_NAME + ) + assert NEW_MAPSET_NAME not in self.testactinia.locations[ + LOCATION_NAME + ].mapsets, \ "Mapset not deleted" # Recreate mapset and delete with Mapset method @@ -182,5 +193,7 @@ def test_actinia_create_and_delete_mapsets(self): ) self.mock_delete() mapset.delete() - assert NEW_MAPSET_NAME not in self.testactinia.locations[LOCATION_NAME].mapsets, \ - "Mapset not deleted" \ No newline at end of file + assert NEW_MAPSET_NAME not in self.testactinia.locations[ + LOCATION_NAME + ].mapsets, \ + "Mapset not deleted" From eb9baf682246d12bcaa741f7642042a2710cffef Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 13:42:17 -0400 Subject: [PATCH 08/19] Fixed bug where location_name is set twice during creation --- actinia/location.py | 1 - 1 file changed, 1 deletion(-) diff --git a/actinia/location.py b/actinia/location.py index 7605c40..08b7b68 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -111,7 +111,6 @@ def create_mapset(self, name): self.__actinia, self.__auth ) - mapset.location = self.name # We could also fetch data from the server again # with self.__request_mapsets() to ensure data is stale self.mapsets[name] = mapset From 97c58ff223a87b5055479b7072c2eff5600ff7a9 Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 14:40:43 -0400 Subject: [PATCH 09/19] Fixed bug when requesting url and added tests to validate correct url during request --- actinia/mapset.py | 6 ++-- tests/mock/actinia_mock.py | 2 ++ tests/test_actinia_mapset_management.py | 39 +++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/actinia/mapset.py b/actinia/mapset.py index e08d9f7..b7c7c2d 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -80,13 +80,13 @@ def __request_url( (info) route: /locations/{location_name}/mapsets/{mapset_name}/info (lock) route: - /locations/{location_name}/mapsets/{mapset_name}/raster_layers + /locations/{location_name}/mapsets/{mapset_name}/lock (raster_layers) route: /locations/{location_name}/mapsets/{mapset_name}/raster_layers (vector_layers) route: /locations/{location_name}/mapsets/{mapset_name}/vector_layers (strds) route: - /locations/{location_name}/mapsets/{mapset_name}/strds - GET + /locations/{location_name}/mapsets/{mapset_name}/strds (processing) route: /locations/{location_name}/mapsets/{mapset_name}/processing (processing_async) route: @@ -102,7 +102,7 @@ def __request_url( if mapset_name is not None: base_url = f"{base_url}/{mapset_name}" if isinstance(task, MAPSET_TASK): - base_url = f"{base_url}/{mapset_name}{task.value}" + base_url = f"{base_url}/{task.value}" return base_url diff --git a/tests/mock/actinia_mock.py b/tests/mock/actinia_mock.py index 6fe5482..99bfa01 100644 --- a/tests/mock/actinia_mock.py +++ b/tests/mock/actinia_mock.py @@ -29,6 +29,8 @@ ACTINIA_BASEURL = "http://localhost:8088" ACTINIA_VERSION = "v3" +ACTINIA_TEST_AUTH = ("user", "pw") +ACTINIA_API_PREFIX = "latest" version_resp_text = { "grass_version": { diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index f33ba55..dbe86fc 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -36,6 +36,8 @@ from .mock.actinia_mock import ( ACTINIA_BASEURL, + ACTINIA_TEST_AUTH, + ACTINIA_API_PREFIX, version_resp_text, location_get_mapset_resp, ) @@ -55,7 +57,6 @@ LOCATION_NAME = "nc_spm_08" NEW_LOCATION_NAME = "test_location" EPSGCODE = 25832 -LOCATION_NAME = "nc_spm_08" MAPSET_NAME = "test_mapset" NEW_MAPSET_NAME = "new_test_mapset" PC = { @@ -121,7 +122,11 @@ def test_location_get_mapsets(self): self.mock_get.return_value.status_code = 200 self.mock_get.return_value.text = json.dumps(location_get_mapset_resp) resp = self.testactinia.locations[LOCATION_NAME].get_mapsets() - + self.mock_get.assert_called_with( + f"{ACTINIA_BASEURL}/{ACTINIA_API_PREFIX}" + + f"/locations/{LOCATION_NAME}/mapsets", + auth=ACTINIA_TEST_AUTH + ) assert isinstance(resp, dict), "response is not a dictionary" assert "PERMANENT" in resp, "'PERMANENT' mapset not in response" assert isinstance( @@ -138,6 +143,12 @@ def test_mapset_get_info(self): LOCATION_NAME ].mapsets[MAPSET_NAME].info() + self.mock_get.assert_called_with( + f"{ACTINIA_BASEURL}/{ACTINIA_API_PREFIX}" + + f"/locations/{LOCATION_NAME}" + + f"/mapsets/{MAPSET_NAME}/info", + auth=ACTINIA_TEST_AUTH + ) assert "region" in resp, "'region' not in location info" assert "projection" in resp, "'projection' not in location info" assert isinstance(resp["projection"], str), "'projection' wrong type" @@ -168,6 +179,12 @@ def test_actinia_create_and_delete_mapsets(self): mapset = self.testactinia.locations[LOCATION_NAME].create_mapset( NEW_MAPSET_NAME ) + self.mock_post.assert_called_with( + f"{ACTINIA_BASEURL}/{ACTINIA_API_PREFIX}" + + f"/locations/{LOCATION_NAME}" + + f"/mapsets/{NEW_MAPSET_NAME}", + auth=ACTINIA_TEST_AUTH + ) assert isinstance(mapset, Mapset), \ "Created mapset is not of type Mapset" assert mapset.name == NEW_MAPSET_NAME, \ @@ -182,6 +199,12 @@ def test_actinia_create_and_delete_mapsets(self): self.testactinia.locations[LOCATION_NAME].delete_mapset( NEW_MAPSET_NAME ) + self.mock_delete.assert_called_with( + f"{ACTINIA_BASEURL}/{ACTINIA_API_PREFIX}" + + f"/locations/{LOCATION_NAME}" + + f"/mapsets/{NEW_MAPSET_NAME}", + auth=ACTINIA_TEST_AUTH + ) assert NEW_MAPSET_NAME not in self.testactinia.locations[ LOCATION_NAME ].mapsets, \ @@ -191,8 +214,20 @@ def test_actinia_create_and_delete_mapsets(self): mapset = self.testactinia.locations[LOCATION_NAME].create_mapset( NEW_MAPSET_NAME ) + self.mock_post.assert_called_with( + f"{ACTINIA_BASEURL}/{ACTINIA_API_PREFIX}" + + f"/locations/{LOCATION_NAME}" + + f"/mapsets/{NEW_MAPSET_NAME}", + auth=ACTINIA_TEST_AUTH + ) self.mock_delete() mapset.delete() + self.mock_delete.assert_called_with( + f"{ACTINIA_BASEURL}/{ACTINIA_API_PREFIX}" + + f"/locations/{LOCATION_NAME}" + + f"/mapsets/{NEW_MAPSET_NAME}", + auth=ACTINIA_TEST_AUTH + ) assert NEW_MAPSET_NAME not in self.testactinia.locations[ LOCATION_NAME ].mapsets, \ From a185018fb56a6f053724588bb1c223790ae01128 Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 15:02:43 -0400 Subject: [PATCH 10/19] Updated docs list_mapset_request docs --- actinia/mapset.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/actinia/mapset.py b/actinia/mapset.py index b7c7c2d..17e55e0 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -132,7 +132,7 @@ def delete(self): @classmethod def list_mapsets_request(cls, location_name, actinia, auth): """ - Creates a mapset within a location. + Lists mapsets within a location. Parameters ---------- @@ -144,8 +144,9 @@ def list_mapsets_request(cls, location_name, actinia, auth): Actinia authentication Returns ------- - Mapset - A new mapset instance for the created mapset + mapsets : dict[mapset_name, Mapset] + A dict of with keys equal to the mapset name and + values set to the Mapset class instance. """ url = cls.__request_url(actinia.url, location_name) resp = requests.get(url, auth=auth) From ac05a4f355be4359b3d98b27d8b2e82ffe9d297d Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 15:18:30 -0400 Subject: [PATCH 11/19] Updated docs --- actinia/mapset.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/actinia/mapset.py b/actinia/mapset.py index 17e55e0..5a5a493 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -142,11 +142,18 @@ def list_mapsets_request(cls, location_name, actinia, auth): An Actinia instance containing the url auth : Actinia authentication + Returns ------- mapsets : dict[mapset_name, Mapset] A dict of with keys equal to the mapset name and values set to the Mapset class instance. + + Raises + ------ + Exception + Error string with response status code + and text if request fails. """ url = cls.__request_url(actinia.url, location_name) resp = requests.get(url, auth=auth) @@ -175,10 +182,17 @@ def create_mapset_request(cls, mapset_name, location_name, actinia, auth): An Actinia instance containing the url auth : Actinia authentication + Returns ------- Mapset A new mapset instance for the created mapset + + Raises + ------ + Exception + Error string with response status code + and text if request fails. """ url = cls.__request_url(actinia.url, location_name, mapset_name) resp = requests.post(url, auth=(auth)) @@ -201,6 +215,16 @@ def delete_mapset_request(cls, mapset_name, location_name, actinia, auth): An Actinia instance containing the url auth : Actinia authentication + + Returns + ------- + None + + Raises + ------ + Exception + Error string with response status code + and text if request fails. """ url = cls.__request_url(actinia.url, location_name, mapset_name) resp = requests.delete(url, auth=(auth)) @@ -211,7 +235,7 @@ def delete_mapset_request(cls, mapset_name, location_name, actinia, auth): @classmethod def request_info(cls, mapset_name, location_name, actinia, auth): """ - Delete a mapset within a location. + Gets detailed info about a mapset. Parameters ---------- @@ -223,6 +247,17 @@ def request_info(cls, mapset_name, location_name, actinia, auth): An Actinia instance containing the url auth : Actinia authentication + + Returns + ------- + dict + Returns JSON process results if successful. + + Raises + ------ + Exception + Error string with response status code + and text if request fails. """ url = cls.__request_url( actinia.url, From 785167dbcaabdb6f2cea1ecfda50ae1cf8cc23ef Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 15:21:54 -0400 Subject: [PATCH 12/19] Fixed typo --- tests/test_actinia_mapset_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index dbe86fc..1c23325 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # From cd5e6a8a816101ca435372b2609af1c19d7bdfea Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 15:27:52 -0400 Subject: [PATCH 13/19] Fixed sistemic typo in file headers ('acintia' -> 'actinia') --- actinia/__init__.py | 2 +- actinia/actinia.py | 2 +- actinia/job.py | 2 +- actinia/location.py | 2 +- actinia/mapset.py | 2 +- actinia/region.py | 2 +- actinia/strds.py | 2 +- actinia/vector.py | 2 +- tests/__init__.py | 2 +- tests/mock/__init__.py | 2 +- tests/mock/actinia_location_management_mock.py | 2 +- tests/mock/actinia_mapset_management_mock.py | 2 +- tests/mock/actinia_mock.py | 2 +- tests/mock/actinia_process_chain_validation_mock.py | 2 +- tests/test_actinia_base.py | 2 +- tests/test_actinia_location_management.py | 2 +- tests/test_actinia_processing.py | 2 +- tests/test_process_chain_validation.py | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/actinia/__init__.py b/actinia/__init__.py index 89b7b8e..8740f05 100644 --- a/actinia/__init__.py +++ b/actinia/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/actinia.py b/actinia/actinia.py index f2b0962..b4a1bce 100644 --- a/actinia/actinia.py +++ b/actinia/actinia.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/job.py b/actinia/job.py index 6b62607..b512690 100644 --- a/actinia/job.py +++ b/actinia/job.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/location.py b/actinia/location.py index 08b7b68..0c656ae 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/mapset.py b/actinia/mapset.py index 5a5a493..a5ebb1c 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/region.py b/actinia/region.py index b62753e..1db116e 100644 --- a/actinia/region.py +++ b/actinia/region.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/strds.py b/actinia/strds.py index 25def6e..746f382 100644 --- a/actinia/strds.py +++ b/actinia/strds.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/actinia/vector.py b/actinia/vector.py index 8ebed34..3dd330f 100644 --- a/actinia/vector.py +++ b/actinia/vector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/__init__.py b/tests/__init__.py index 6f40733..bb5205e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/mock/__init__.py b/tests/mock/__init__.py index 6f40733..bb5205e 100644 --- a/tests/mock/__init__.py +++ b/tests/mock/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/mock/actinia_location_management_mock.py b/tests/mock/actinia_location_management_mock.py index 5459e79..23365a6 100644 --- a/tests/mock/actinia_location_management_mock.py +++ b/tests/mock/actinia_location_management_mock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/mock/actinia_mapset_management_mock.py b/tests/mock/actinia_mapset_management_mock.py index 50138d8..8f3cbd4 100644 --- a/tests/mock/actinia_mapset_management_mock.py +++ b/tests/mock/actinia_mapset_management_mock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/mock/actinia_mock.py b/tests/mock/actinia_mock.py index 99bfa01..fae2a6e 100644 --- a/tests/mock/actinia_mock.py +++ b/tests/mock/actinia_mock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/mock/actinia_process_chain_validation_mock.py b/tests/mock/actinia_process_chain_validation_mock.py index 9ed46b2..66e48d0 100644 --- a/tests/mock/actinia_process_chain_validation_mock.py +++ b/tests/mock/actinia_process_chain_validation_mock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/test_actinia_base.py b/tests/test_actinia_base.py index e2bd624..2a502d1 100644 --- a/tests/test_actinia_base.py +++ b/tests/test_actinia_base.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/test_actinia_location_management.py b/tests/test_actinia_location_management.py index 088d049..8755ca1 100644 --- a/tests/test_actinia_location_management.py +++ b/tests/test_actinia_location_management.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/test_actinia_processing.py b/tests/test_actinia_processing.py index d19e99d..163e7e2 100644 --- a/tests/test_actinia_processing.py +++ b/tests/test_actinia_processing.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # diff --git a/tests/test_process_chain_validation.py b/tests/test_process_chain_validation.py index 55a160e..f45427a 100644 --- a/tests/test_process_chain_validation.py +++ b/tests/test_process_chain_validation.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ####### -# acintia-python-client is a python client for actinia - an open source REST +# actinia-python-client is a python client for actinia - an open source REST # API for scalable, distributed, high performance processing of geographical # data that uses GRASS GIS for computational tasks. # From c1d2cccd91567f8e926f02d747717a7a027fcb38 Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 15:30:22 -0400 Subject: [PATCH 14/19] fixed typo in docs --- actinia/location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actinia/location.py b/actinia/location.py index 0c656ae..5d010d3 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -103,7 +103,7 @@ def get_mapsets(self): def create_mapset(self, name): """ - Creates a mapset with in the location. + Creates a mapset within the location. """ mapset = Mapset.create_mapset_request( name, From c7726256623b2375cce0657e8afa09d131184c7e Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 16 Jun 2022 15:31:50 -0400 Subject: [PATCH 15/19] fixed typo in docs --- actinia/location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actinia/location.py b/actinia/location.py index 5d010d3..d5d681f 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -118,7 +118,7 @@ def create_mapset(self, name): def delete_mapset(self, name): """ - Deletes a mapset and return update mapseet list for the location. + Deletes a mapset and returns an updated mapset list for the location. """ Mapset.delete_mapset_request( name, From 3b6008cb156c83860a714d93d532341882250c09 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 21 Jun 2022 13:30:49 -0400 Subject: [PATCH 16/19] Removed unneeded method --- tests/test_actinia_mapset_management.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_actinia_mapset_management.py b/tests/test_actinia_mapset_management.py index 1c23325..24156df 100644 --- a/tests/test_actinia_mapset_management.py +++ b/tests/test_actinia_mapset_management.py @@ -110,12 +110,6 @@ def mock_delete_mapset(cls): cls.mock_delete.return_value.status_code = 200 cls.mock_delete.return_value.text = json.dumps(delete_mapset_resp) - @classmethod - def mock_delete(cls): - cls.mock_delete.return_value = Mock() - cls.mock_delete.return_value.status_code = 200 - cls.mock_delete.return_value.text = json.dumps(delete_mapset_resp) - def test_location_get_mapsets(self): """Test location get_mapsets method.""" self.mock_get.return_value = Mock() From d63ee8c06161a740daf1666cfc644101df64cce8 Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 8 Jul 2022 13:12:10 -0400 Subject: [PATCH 17/19] Merged with main and fixed conflicts --- Makefile | 16 +- README-dev.md | 8 + actinia/job.py | 1 + actinia/location.py | 2 +- actinia/mapset.py | 156 +++++- actinia/raster.py | 76 +++ actinia/utils.py | 56 ++ actinia/vector.py | 51 ++ docs/docs/06_raster_vector_strds_managment.md | 82 +++ ...tion.md => 07_process_chain_validation.md} | 0 .../{07_processing.md => 08_processing.md} | 0 docs/docs/index.md | 10 +- tests/data/elevation.tif | Bin 0 -> 184838 bytes tests/data/firestations.geojson | 78 +++ tests/mock/actinia_raster_management_mock.py | 391 +++++++++++++ tests/mock/actinia_vector_management_mock.py | 527 ++++++++++++++++++ tests/test_actinia_raster_management.py | 151 +++++ tests/test_actinia_vector_management.py | 150 +++++ 18 files changed, 1746 insertions(+), 9 deletions(-) create mode 100644 actinia/raster.py create mode 100644 actinia/utils.py create mode 100644 docs/docs/06_raster_vector_strds_managment.md rename docs/docs/{06_process_chain_validation.md => 07_process_chain_validation.md} (100%) rename docs/docs/{07_processing.md => 08_processing.md} (100%) create mode 100644 tests/data/elevation.tif create mode 100644 tests/data/firestations.geojson create mode 100644 tests/mock/actinia_raster_management_mock.py create mode 100644 tests/mock/actinia_vector_management_mock.py create mode 100644 tests/test_actinia_raster_management.py create mode 100644 tests/test_actinia_vector_management.py diff --git a/Makefile b/Makefile index d8354ac..3e87eae 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,21 @@ SRC := . +.PHONY: dependencies + +dependencies: + pip install -r $(SRC)/requirements.txt + install: pip install actinia-python-client -installdev: - pip install -r $(SRC)/requirements.txt +installdev: dependencies pip install -e . -test: - pip install -r $(SRC)/requirements.txt +test: dependencies pip install pytest python -m pytest tests/ + +devtest: + pip install -r $(SRC)/requirements.txt + pip install pytest + python -m pytest tests/ -m dev diff --git a/README-dev.md b/README-dev.md index 9670081..71e7f77 100644 --- a/README-dev.md +++ b/README-dev.md @@ -20,4 +20,12 @@ test = Actinia() ## Run tests ``` make test +make devtest ``` +To run only a few tests you can mark the tests for development with +`@pytest.mark.dev` and add `import pytest` to the `.py` file/s with the tests +you want to run. Then run +``` +make devtest +``` +(This fails if no test is marked!) diff --git a/actinia/job.py b/actinia/job.py index b512690..7732885 100644 --- a/actinia/job.py +++ b/actinia/job.py @@ -108,6 +108,7 @@ def poll(self): except requests.exceptions.ConnectionError as e: raise e resp = json.loads(actiniaResp.text) + if "process_results" not in resp: resp["process_results"] = {} self.__update( diff --git a/actinia/location.py b/actinia/location.py index d5d681f..636af07 100644 --- a/actinia/location.py +++ b/actinia/location.py @@ -95,7 +95,7 @@ def delete(self): def get_mapsets(self): """ - Return location information + Return mapsets """ if self.mapsets is None: self.__request_mapsets() diff --git a/actinia/mapset.py b/actinia/mapset.py index a5ebb1c..a41f991 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -31,6 +31,10 @@ import requests from actinia.region import Region from enum import Enum, unique +from actinia.raster import Raster +from actinia.vector import Vector +from actinia.utils import request_and_check, print_stdout +from actinia.job import Job @unique @@ -103,7 +107,6 @@ def __request_url( base_url = f"{base_url}/{mapset_name}" if isinstance(task, MAPSET_TASK): base_url = f"{base_url}/{task.value}" - return base_url def info(self): @@ -271,11 +274,160 @@ def request_info(cls, mapset_name, location_name, actinia, auth): proc_res = json.loads(resp.text)["process_results"] return proc_res + def __request_raster_layers(self): + """ + Requests the raster layers in the mapset. + + :return: A list of the raster maps + """ + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.name}/raster_layers" + resp = request_and_check(url, auth=self.__auth) + raster_names = resp["process_results"] + rasters = { + mname: Raster( + mname, self.__location_name, self.name, + self.__actinia, self.__auth + ) + for mname in raster_names + } + self.raster_layers = rasters + + def get_raster_layers(self, force=False): + """ + Return raster layers of the mapset + """ + if self.raster_layers is None or force is True: + self.__request_raster_layers() + return self.raster_layers + + def __request_vector_layers(self): + """ + Requests the vector layers in the mapset. + + :return: A list of the vector maps + """ + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.name}/vector_layers" + resp = request_and_check(url, auth=self.__auth) + vector_names = resp["process_results"] + vectors = { + mname: Vector( + mname, self.__location_name, self.name, + self.__actinia, self.__auth + ) + for mname in vector_names + } + self.vector_layers = vectors + + def get_vector_layers(self, force=False): + """ + Return vector layers of the mapset + """ + if self.vector_layers is None or force is True: + self.__request_vector_layers() + return self.vector_layers + + def upload_raster(self, layer_name, tif_file): + """Upload GTiff as a raster layer + Parameters: + layer_name (string): Name for the raster layer to create + tif_file (string): Path of the GTiff file to upload + """ + files = {"file": (tif_file, open(tif_file, "rb"))} + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.name}/raster_layers/{layer_name}" + resp = requests.post( + url=url, + files=files, + auth=self.__auth, + ) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + kwargs = json.loads(resp.text) + job = Job( + f"raster_upload_{self.__location_name}_{self.name}_{layer_name}", + self.__actinia, self.__auth, **kwargs) + job.poll_until_finished() + if job.status != "finished": + raise Exception(f"{job.status}: {job.message}") + if self.raster_layers is None: + self.get_raster_layers() + self.raster_layers[layer_name] = Raster( + layer_name, self.__location_name, self.name, + self.__actinia, self.__auth + ) + + def delete_raster(self, layer_name): + """Delete a raster layer""" + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.name}/raster_layers/{layer_name}" + resp = requests.delete( + url=url, + auth=self.__auth, + ) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + if self.raster_layers is None: + self.get_raster_layers() + else: + del self.raster_layers[layer_name] + print_stdout(f"Raster <{layer_name}> successfully deleted") + + def upload_vector(self, layer_name, vector_file): + """Upload vector file (GPKG, zipped Shapefile or GeoJSON) as a vector + layer. + Parameters: + layer_name (string): Name for the vector layer to create + vector_file (string): Path of the GPKG/zipped Shapefile or GeoJSON + to upload + """ + files = {"file": (vector_file, open(vector_file, "rb"))} + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.name}/vector_layers/{layer_name}" + resp = requests.post( + url=url, + files=files, + auth=self.__auth, + ) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + kwargs = json.loads(resp.text) + job = Job( + f"vector_upload_{self.__location_name}_{self.name}_{layer_name}", + self.__actinia, self.__auth, **kwargs) + job.poll_until_finished() + if job.status != "finished": + raise Exception(f"{job.status}: {job.message}") + if self.vector_layers is None: + self.get_vector_layers() + self.vector_layers[layer_name] = Vector( + layer_name, self.__location_name, self.name, + self.__actinia, self.__auth + ) + + def delete_vector(self, layer_name): + """Delete a vector layer""" + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.name}/vector_layers/{layer_name}" + resp = requests.delete( + url=url, + auth=self.__auth, + ) + if resp.status_code != 200: + raise Exception(f"Error {resp.status_code}: {resp.text}") + if self.vector_layers is None: + self.get_vector_layers() + else: + del self.vector_layers[layer_name] + print_stdout(f"Vector <{layer_name}> successfully deleted") + + # TODO: # * (/locations/{location_name}/mapsets/{mapset_name}/lock - GET, DELETE, POST) # * /locations/{location_name}/mapsets/{mapset_name}/raster_layers -# - DELETE, GET, PUT +# - DELETE, PUT # * /locations/{location_name}/mapsets/{mapset_name}/strds - GET # * "/locations/{location_name}/mapsets/{mapset_name}/vector_layers" diff --git a/actinia/raster.py b/actinia/raster.py new file mode 100644 index 0000000..1e571ad --- /dev/null +++ b/actinia/raster.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# actinia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "Anika Weinmann" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "Anika Weinmann" + +from actinia.region import Region +from actinia.utils import request_and_check + + +class Raster: + def __init__(self, name, location_name, mapset_name, actinia, auth): + self.name = name + self.__location_name = location_name + self.__mapset_name = mapset_name + self.__actinia = actinia + self.__auth = auth + self.region = None + self.info = None + + def get_info(self): + """Return the information of the raster map + """ + if self.info is None: + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.__mapset_name}/raster_layers/{self.name}" + resp = request_and_check(url, self.__auth) + r_info = resp["process_results"] + self.info = r_info + + self.region = Region( + zone=None, + projection=None, + n=r_info["north"], + s=r_info["south"], + e=r_info["east"], + w=r_info["west"], + t=None, + b=None, + nsres=r_info["nsres"], + ewres=r_info["ewres"], + nsres3=None, + ewres3=None, + tbres=None, + rows=r_info["rows"], + cols=r_info["cols"], + rows3=None, + cols3=None, + depths=None, + cells=r_info["cells"], + cells3=None + ) + return self.info diff --git a/actinia/utils.py b/actinia/utils.py new file mode 100644 index 0000000..819db54 --- /dev/null +++ b/actinia/utils.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# actinia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "Anika Weinmann" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "Anika Weinmann" + +import json +import requests +import sys + + +def request_and_check(url, auth, status_code=200): + """Function to send a GET request to an URL and check the status code. + + Parameters: + url (string): URL as string + auth (tuple): Tupel of user and password + status_code (int): Status code to check if it is set; default is 200 + + Returns: + (dict): returns text of the response as dictionary + + Throws an error if the request does not have the status_code + """ + resp = requests.get(url, auth=auth) + if resp.status_code != status_code: + raise Exception(f"Error {resp.status_code}: {resp.text}") + return json.loads(resp.text) + + +def print_stdout(msg): + """Print message to stdout""" + print(msg, file=sys.stdout) diff --git a/actinia/vector.py b/actinia/vector.py index 3dd330f..8479b85 100644 --- a/actinia/vector.py +++ b/actinia/vector.py @@ -27,6 +27,57 @@ __copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" __maintainer__ = "Anika Weinmann" +import json + +from actinia.region import Region +from actinia.utils import request_and_check, print_stdout + + +class Vector: + def __init__(self, name, location_name, mapset_name, actinia, auth): + self.name = name + self.__location_name = location_name + self.__mapset_name = mapset_name + self.__actinia = actinia + self.__auth = auth + self.region = None + self.info = None + + def get_info(self): + """Return the information of the vector map + """ + if self.info is None: + url = f"{self.__actinia.url}/locations/{self.__location_name}/" \ + f"mapsets/{self.__mapset_name}/vector_layers/{self.name}" + resp = request_and_check(url, self.__auth) + v_info = resp["process_results"] + self.info = v_info + + self.region = Region( + zone=None, + projection=None, + n=v_info["north"], + s=v_info["south"], + e=v_info["east"], + w=v_info["west"], + t=v_info["top"], + b=v_info["bottom"], + nsres=None, + ewres=None, + nsres3=None, + ewres3=None, + tbres=None, + rows=None, + cols=None, + rows3=None, + cols3=None, + depths=None, + cells=None, + cells3=None + ) + print_stdout(json.dumps(self.info, indent=4)) + return self.info + # TODO: # * /locations/{location_name}/mapsets/{mapset_name}/vector_layers # - DELETE, GET, PUT diff --git a/docs/docs/06_raster_vector_strds_managment.md b/docs/docs/06_raster_vector_strds_managment.md new file mode 100644 index 0000000..940d8f1 --- /dev/null +++ b/docs/docs/06_raster_vector_strds_managment.md @@ -0,0 +1,82 @@ +# Raster, Vector and STRDS Management + +``` +from actinia import Actinia + +actinia_mundialis = Actinia() +actinia_mundialis.get_version() +actinia_mundialis.set_authentication("demouser", "gu3st!pa55w0rd") + +# request all locations +locations = actinia_mundialis.get_locations() +# Get Mapsets of nc_spm_08 location +mapsets = actinia_mundialis.locations["nc_spm_08"].get_mapsets() +``` + +## Raster manangement + +Get all rasters of the `PERMANENT` mapsets +``` +rasters = mapsets["PERMANENT"].get_raster_layers() +print(rasters.keys()) +``` + +Get information of the raster `zipcodes` +``` +info = rasters["zipcodes"].get_info() +``` + +Upload a GTif as raster layer to a user mapset (here the user mapset will be +created before) +``` +# TODO add mapset creation +mapset_name = "test_mapset" + +# upload tif +raster_layer_name = "test" +file = "/home/testuser/data/elevation.tif" +locations["nc_spm_08"].mapsets[mapset_name].upload_raster(raster_layer_name, file) +print(locations["nc_spm_08"].mapsets[mapset_name].raster_layers.keys()) +``` + +Delete a raster layer +``` +locations["nc_spm_08"].mapsets[mapset_name].delete_raster(raster_layer_name) +print(locations["nc_spm_08"].mapsets[mapset_name].raster_layers.keys()) + +# TODO delete mapset +``` + +## Vector management + +Get all vector maps of the `PERMANENT` mapsets +``` +vectors = mapsets["PERMANENT"].get_vector_layers() +print(vectors.keys()) +``` + +Get information of the vector `boundary_county` +``` +info = vectors["boundary_county"].get_info() +``` + +Upload a GeoJSON as vector layer to a user mapset (here the user mapset will +be created before) +``` +# TODO add mapset creation +mapset_name = "test_mapset" + +# upload tif +vector_layer_name = "test" +file = "/home/testuser/data/firestations.geojson" +locations["nc_spm_08"].mapsets[mapset_name].upload_vector(vector_layer_name, file) +print(locations["nc_spm_08"].mapsets[mapset_name].vector_layers.keys()) +``` + +Delete a raster layer +``` +locations["nc_spm_08"].mapsets[mapset_name].delete_vector(vector_layer_name) +print(locations["nc_spm_08"].mapsets[mapset_name].vector_layers.keys()) + +# TODO delete mapset +``` diff --git a/docs/docs/06_process_chain_validation.md b/docs/docs/07_process_chain_validation.md similarity index 100% rename from docs/docs/06_process_chain_validation.md rename to docs/docs/07_process_chain_validation.md diff --git a/docs/docs/07_processing.md b/docs/docs/08_processing.md similarity index 100% rename from docs/docs/07_processing.md rename to docs/docs/08_processing.md diff --git a/docs/docs/index.md b/docs/docs/index.md index 2275a1c..e37b8ac 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -2,6 +2,11 @@ + + + Fork Me On GitHub + + Access [**actinia - The GRASS GIS REST API**](https://actinia.mundialis.de/) via Python. @@ -11,5 +16,6 @@ Access [**actinia - The GRASS GIS REST API**](https://actinia.mundialis.de/) via * [Quickstart](03_quickstart.md) * [Location Management](04_location_management.md) * [Mapset Management](05_mapset_managment.md) -* [Process Chain Validation](06_process_chain_validation.md) -* [Processing](07_processing.md) +* [Raster, Vector, STRDS Management](06_raster_vector_strds_managment.md) +* [Process Chain Validation](07_process_chain_validation.md) +* [Processing](08_processing.md) diff --git a/tests/data/elevation.tif b/tests/data/elevation.tif new file mode 100644 index 0000000000000000000000000000000000000000..86ea77bb10acf49c8b1f472486a838060cc53ec3 GIT binary patch literal 184838 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 Mn8@P)=VbB!02f1OWdHyG literal 0 HcmV?d00001 diff --git a/tests/data/firestations.geojson b/tests/data/firestations.geojson new file mode 100644 index 0000000..82405f1 --- /dev/null +++ b/tests/data/firestations.geojson @@ -0,0 +1,78 @@ +{ +"type": "FeatureCollection", +"name": "firestations", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3358" } }, +"features": [ +{ "type": "Feature", "properties": { "cat": 1, "ID": 24, "LABEL": "Morrisville #3", "LOCATION": "6804 Carpenter Fire Station Rd", "CITY": "Morrisville", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 3, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "240", "AGENCY": "FD", "STATIONID": "MF3A", "RECNO": 1.0, "CV_SID2": "MF3A", "CVLAG": 1.4 }, "geometry": { "type": "Point", "coordinates": [ 620856.95858763367869, 230066.383132105489494 ] } }, +{ "type": "Feature", "properties": { "cat": 2, "ID": 23, "LABEL": "Morrisville #1", "LOCATION": "100 Morrisville-Carpenter Rd", "CITY": "Morrisville", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 1, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 3, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "241", "AGENCY": "FD", "STATIONID": "MF1A", "RECNO": 2.0, "CV_SID2": "MF1A", "CVLAG": 1.4 }, "geometry": { "type": "Point", "coordinates": [ 625331.918597490759566, 229990.821607626159675 ] } }, +{ "type": "Feature", "properties": { "cat": 3, "ID": 40, "LABEL": "Apex #2", "LOCATION": "3045 New Hill Holleman Rd", "CITY": "Apex", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 1, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "242", "AGENCY": "FD", "STATIONID": "AF2A", "RECNO": 3.0, "CV_SID2": "AF2A", "CVLAG": 1.4 }, "geometry": { "type": "Point", "coordinates": [ 615797.665474870009348, 213363.992619827244198 ] } }, +{ "type": "Feature", "properties": { "cat": 4, "ID": 4, "LABEL": "Apex #1", "LOCATION": "210 N. Salem St.", "CITY": "Apex", "MUN_COUNT": "C", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 2, "AERIAL": 1, "BRUSH": 1, "OTHERS": 2, "WATER_RESC": 2, "MUNCOID": 2, "BLDGCODE": "243", "AGENCY": "FD", "STATIONID": "AF1A", "RECNO": 4.0, "CV_SID2": "AF1A", "CVLAG": 1.4 }, "geometry": { "type": "Point", "coordinates": [ 623113.714241653447971, 219859.062221608735854 ] } }, +{ "type": "Feature", "properties": { "cat": 5, "ID": 7, "LABEL": "Fuquay-Varina #1", "LOCATION": "301 S. Fuquay Av", "CITY": "Fuquay-Varina", "MUN_COUNT": "M", "PUMPERS": 4, "PUMPER_TAN": 0, "TANKER": 2, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 3, "WATER_RESC": 1, "MUNCOID": 1, "BLDGCODE": "246", "AGENCY": "FD", "STATIONID": "FV2A", "RECNO": 7.0, "CV_SID2": "FV2A", "CVLAG": 1.33 }, "geometry": { "type": "Point", "coordinates": [ 627926.284320811857469, 203208.616053598729195 ] } }, +{ "type": "Feature", "properties": { "cat": 6, "ID": 33, "LABEL": "Fuquay-Varina #2", "LOCATION": "5617 Hilltop Rd", "CITY": "Fuquay-Varina", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 1, "TANKER": 2, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "247", "AGENCY": "FD", "STATIONID": "FV1A", "RECNO": 8.0, "CV_SID2": "FV1A", "CVLAG": 1.33 }, "geometry": { "type": "Point", "coordinates": [ 634666.664788150810637, 207474.493886174517684 ] } }, +{ "type": "Feature", "properties": { "cat": 7, "ID": 18, "LABEL": "Garner #2", "LOCATION": "9115 Sauls Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 2, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "248", "AGENCY": "FD", "STATIONID": "GF2A", "RECNO": 9.0, "CV_SID2": "GF2A", "CVLAG": 1.82 }, "geometry": { "type": "Point", "coordinates": [ 642099.609982577036135, 208679.469462198845576 ] } }, +{ "type": "Feature", "properties": { "cat": 8, "ID": 34, "LABEL": "Fairview #2", "LOCATION": "7401 Ten-Ten Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 1, "TANKER": 1, "MINI_PUMPE": 1.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "249", "AGENCY": "FD", "STATIONID": "FF2A", "RECNO": 10.0, "CV_SID2": "FF2A", "CVLAG": 1.42 }, "geometry": { "type": "Point", "coordinates": [ 636078.571595257380977, 213276.047743950621225 ] } }, +{ "type": "Feature", "properties": { "cat": 9, "ID": 20, "LABEL": "Fairview #1", "LOCATION": "4501 Ten-Ten Rd", "CITY": "Apex", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 1, "TANKER": 3, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "162", "AGENCY": "FD", "STATIONID": "FF1A", "RECNO": 11.0, "CV_SID2": "FF1A", "CVLAG": 1.42 }, "geometry": { "type": "Point", "coordinates": [ 630420.500347112072632, 215694.028075590264052 ] } }, +{ "type": "Feature", "properties": { "cat": 10, "ID": 444, "LABEL": "Cary #4", "LOCATION": "1401 Old Apex Rd", "CITY": "Cary", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 2, "AERIAL": 0, "BRUSH": 0, "OTHERS": 2, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "250", "AGENCY": "CF", "STATIONID": "CF4A", "RECNO": 12.0, "CV_SID2": "CF4A", "CVLAG": 1.0 }, "geometry": { "type": "Point", "coordinates": [ 627041.549100277363323, 224084.253259528632043 ] } }, +{ "type": "Feature", "properties": { "cat": 11, "ID": 555, "LABEL": "Cary #5", "LOCATION": "2101 High House Rd", "CITY": "Cary", "MUN_COUNT": "M", "PUMPERS": 3, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "251", "AGENCY": "CF", "STATIONID": "CF5A", "RECNO": 13.0, "CV_SID2": "CF5A", "CVLAG": 1.0 }, "geometry": { "type": "Point", "coordinates": [ 622996.306724348571151, 226225.281983900815248 ] } }, +{ "type": "Feature", "properties": { "cat": 12, "ID": 222, "LABEL": "Cary #2", "LOCATION": "875 SE Maynard Rd", "CITY": "Cary", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "252", "AGENCY": "CF", "STATIONID": "CF2A", "RECNO": 14.0, "CV_SID2": "CF2A", "CVLAG": 1.0 }, "geometry": { "type": "Point", "coordinates": [ 630879.211980559630319, 224876.554130168253323 ] } }, +{ "type": "Feature", "properties": { "cat": 13, "ID": 29, "LABEL": "Western Wake #2", "LOCATION": "329 E. Durham Rd", "CITY": "Cary", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 3, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "253", "AGENCY": "FD", "STATIONID": "WW2A", "RECNO": 15.0, "CV_SID2": "WW2A", "CVLAG": 1.22 }, "geometry": { "type": "Point", "coordinates": [ 629930.3831512281904, 226389.519515574385878 ] } }, +{ "type": "Feature", "properties": { "cat": 14, "ID": 111, "LABEL": "Cary #1", "LOCATION": "1501 N. Harrison Av", "CITY": "Cary", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "254", "AGENCY": "CF", "STATIONID": "CF1A", "RECNO": 16.0, "CV_SID2": "CF1A", "CVLAG": 1.0 }, "geometry": { "type": "Point", "coordinates": [ 630123.616555116488598, 229512.652985793101834 ] } }, +{ "type": "Feature", "properties": { "cat": 15, "ID": 30, "LABEL": "RDU CFR", "LOCATION": "1050 Rescue Ct", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 3, "TANKER": 0, "MINI_PUMPE": 1.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 4, "WATER_RESC": 1, "MUNCOID": 1, "BLDGCODE": "255", "AGENCY": "FD", "STATIONID": "RU1A", "RECNO": 17.0, "CV_SID2": "RU1A", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 628813.790661693550646, 236394.507909550215118 ] } }, +{ "type": "Feature", "properties": { "cat": 16, "ID": 17, "LABEL": "RFD #23", "LOCATION": "8312 Pinecrest Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "256", "AGENCY": "RF", "STATIONID": "RF23", "RECNO": 18.0, "CV_SID2": "RF23", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 634267.98816537624225, 238200.760264191863826 ] } }, +{ "type": "Feature", "properties": { "cat": 17, "ID": 0, "LABEL": "RFD #17", "LOCATION": "4601 Pleasant Valley Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "257", "AGENCY": "RF", "STATIONID": "RF17", "RECNO": 19.0, "CV_SID2": "RF17", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 635640.313379963859916, 234048.03643446759088 ] } }, +{ "type": "Feature", "properties": { "cat": 18, "ID": 0, "LABEL": "RFD #14", "LOCATION": "4220 Lake Boone Tr", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "258", "AGENCY": "RF", "STATIONID": "RF14", "RECNO": 20.0, "CV_SID2": "RF14", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 636556.265332862618379, 229202.794538971764268 ] } }, +{ "type": "Feature", "properties": { "cat": 19, "ID": 19, "LABEL": "Western Wake #1", "LOCATION": "4021 District Dr", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 3, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "164", "AGENCY": "FD", "STATIONID": "WW1A", "RECNO": 21.0, "CV_SID2": "WW1A", "CVLAG": 1.22 }, "geometry": { "type": "Point", "coordinates": [ 635775.56533924723044, 228121.69258378498489 ] } }, +{ "type": "Feature", "properties": { "cat": 20, "ID": 0, "LABEL": "RFD #8", "LOCATION": "5001 Western Blvd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "259", "AGENCY": "RF", "STATIONID": "RF08", "RECNO": 22.0, "CV_SID2": "RF08", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 635940.262305415701121, 225912.796458184253424 ] } }, +{ "type": "Feature", "properties": { "cat": 21, "ID": 0, "LABEL": "RFD #20", "LOCATION": "1721 Trailwood Dr", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 2, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "260", "AGENCY": "RF", "STATIONID": "RF20", "RECNO": 23.0, "CV_SID2": "RF20", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 637386.831291471607983, 222569.151597361254971 ] } }, +{ "type": "Feature", "properties": { "cat": 22, "ID": 0, "LABEL": "RFD #2", "LOCATION": "263 Pecan Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "261", "AGENCY": "RF", "STATIONID": "RF02", "RECNO": 24.0, "CV_SID2": "RF02", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 641437.430979470140301, 221737.708437226829119 ] } }, +{ "type": "Feature", "properties": { "cat": 23, "ID": 8, "LABEL": "Garner #1", "LOCATION": "503 W. Main St", "CITY": "Garner", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 3, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "262", "AGENCY": "FD", "STATIONID": "GF1A", "RECNO": 25.0, "CV_SID2": "GF1A", "CVLAG": 1.82 }, "geometry": { "type": "Point", "coordinates": [ 644588.467292286222801, 217450.613751191849587 ] } }, +{ "type": "Feature", "properties": { "cat": 24, "ID": 0, "LABEL": "RFD #10", "LOCATION": "2711 Sanderford Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "263", "AGENCY": "RF", "STATIONID": "RF10", "RECNO": 26.0, "CV_SID2": "RF10", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 644598.620576115325093, 221014.978652047197102 ] } }, +{ "type": "Feature", "properties": { "cat": 25, "ID": 0, "LABEL": "RFD #3", "LOCATION": "13 S. East St", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "264", "AGENCY": "RF", "STATIONID": "RF03", "RECNO": 27.0, "CV_SID2": "RF03", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 642868.291956368950196, 225195.289151038014097 ] } }, +{ "type": "Feature", "properties": { "cat": 26, "ID": 0, "LABEL": "RFD #5", "LOCATION": "300 Oberlin Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "265", "AGENCY": "RF", "STATIONID": "RF05", "RECNO": 28.0, "CV_SID2": "RF05", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 640173.911526122130454, 226162.379877263854723 ] } }, +{ "type": "Feature", "properties": { "cat": 27, "ID": 0, "LABEL": "RFD #6", "LOCATION": "2601 Fairview Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 1, "MUNCOID": 1, "BLDGCODE": "266", "AGENCY": "RF", "STATIONID": "RF06", "RECNO": 29.0, "CV_SID2": "RF06", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 640711.472824260243215, 228461.465771809627768 ] } }, +{ "type": "Feature", "properties": { "cat": 28, "ID": 0, "LABEL": "RFD #7", "LOCATION": "2100 Glascock St", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "267", "AGENCY": "RF", "STATIONID": "RF07", "RECNO": 30.0, "CV_SID2": "RF07", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 644601.880625902209431, 226660.120970335730817 ] } }, +{ "type": "Feature", "properties": { "cat": 29, "ID": 0, "LABEL": "RFD #12", "LOCATION": "3409 Poole Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "268", "AGENCY": "RF", "STATIONID": "RF12", "RECNO": 31.0, "CV_SID2": "RF12", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 647591.542133385664783, 224044.035418901650701 ] } }, +{ "type": "Feature", "properties": { "cat": 30, "ID": 38, "LABEL": "Eastern Wake #1", "LOCATION": "4828 Clifton Rd", "CITY": "Knightdale", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 1, "BRUSH": 1, "OTHERS": 2, "WATER_RESC": 1, "MUNCOID": 2, "BLDGCODE": "269", "AGENCY": "FD", "STATIONID": "EW2", "RECNO": 32.0, "CV_SID2": "EW2A", "CVLAG": 1.83 }, "geometry": { "type": "Point", "coordinates": [ 655179.723751222947612, 222274.59509701130446 ] } }, +{ "type": "Feature", "properties": { "cat": 31, "ID": 13, "LABEL": "Eastern Wake #2", "LOCATION": "401 Hester St", "CITY": "Knightdale", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 1.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "270", "AGENCY": "FD", "STATIONID": "EW1", "RECNO": 33.0, "CV_SID2": "EW1A", "CVLAG": 1.83 }, "geometry": { "type": "Point", "coordinates": [ 656750.460215169587173, 226364.964583368389867 ] } }, +{ "type": "Feature", "properties": { "cat": 32, "ID": 11, "LABEL": "Wendell #1", "LOCATION": "2960 Wendell Blvd", "CITY": "Wendell", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 3, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 1, "BRUSH": 1, "OTHERS": 3, "WATER_RESC": 2, "MUNCOID": 2, "BLDGCODE": "271", "AGENCY": "FD", "STATIONID": "WE1A", "RECNO": 34.0, "CV_SID2": "WE1A", "CVLAG": 1.37 }, "geometry": { "type": "Point", "coordinates": [ 665825.976110845571384, 226513.60771604636102 ] } }, +{ "type": "Feature", "properties": { "cat": 33, "ID": 9, "LABEL": "Zebulon", "LOCATION": "113 E. Vance St", "CITY": "Zebulon", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 1.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 3, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "272", "AGENCY": "FD", "STATIONID": "ZF1A", "RECNO": 35.0, "CV_SID2": "ZF1A", "CVLAG": 1.07 }, "geometry": { "type": "Point", "coordinates": [ 671786.563437098287977, 230059.223819323262433 ] } }, +{ "type": "Feature", "properties": { "cat": 34, "ID": 22, "LABEL": "Hopkins", "LOCATION": "8933 Fowler Rd", "CITY": "Zebulon", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 1.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "273", "AGENCY": "FD", "STATIONID": "HO1A", "RECNO": 36.0, "CV_SID2": "HO1A", "CVLAG": 1.23 }, "geometry": { "type": "Point", "coordinates": [ 667866.383375128847547, 237034.961466459557414 ] } }, +{ "type": "Feature", "properties": { "cat": 35, "ID": 32, "LABEL": "Wendell #2", "LOCATION": "6529 Bethany Church Rd", "CITY": "Wendell", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 1, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "274", "AGENCY": "FD", "STATIONID": "WE2A", "RECNO": 37.0, "CV_SID2": "WE2A", "CVLAG": 1.37 }, "geometry": { "type": "Point", "coordinates": [ 660133.995975293102674, 232499.891850246611284 ] } }, +{ "type": "Feature", "properties": { "cat": 36, "ID": 31, "LABEL": "Wake-New Hope #2", "LOCATION": "3909 Watkins Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 3, "PUMPER_TAN": 0, "TANKER": 3, "MINI_PUMPE": 1.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 1, "MUNCOID": 2, "BLDGCODE": "275", "AGENCY": "FD", "STATIONID": "NH2A", "RECNO": 38.0, "CV_SID2": "NH2A", "CVLAG": 1.63 }, "geometry": { "type": "Point", "coordinates": [ 656418.465109551907517, 234222.717205571767408 ] } }, +{ "type": "Feature", "properties": { "cat": 37, "ID": 15, "LABEL": "Rolesville", "LOCATION": "104 E. Young St", "CITY": "Rolesville", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 2, "TANKER": 2, "MINI_PUMPE": 1.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 4, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "163", "AGENCY": "FD", "STATIONID": "RV1A", "RECNO": 39.0, "CV_SID2": "RV1A", "CVLAG": 1.43 }, "geometry": { "type": "Point", "coordinates": [ 658578.876382684335113, 241140.122121746710036 ] } }, +{ "type": "Feature", "properties": { "cat": 38, "ID": 6, "LABEL": "Wake Forest #1", "LOCATION": "420 E. Elm Av", "CITY": "Wake Forest", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 3, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 2, "OTHERS": 2, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "276", "AGENCY": "FD", "STATIONID": "WF1A", "RECNO": 40.0, "CV_SID2": "WF1A", "CVLAG": 1.48 }, "geometry": { "type": "Point", "coordinates": [ 654051.70407712191809, 246906.879278650798369 ] } }, +{ "type": "Feature", "properties": { "cat": 39, "ID": 21, "LABEL": "Falls", "LOCATION": "11908 Falls of Neuse Rd", "CITY": "Wake Forest", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 3, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 1, "MUNCOID": 2, "BLDGCODE": "278", "AGENCY": "FD", "STATIONID": "FL1A", "RECNO": 41.0, "CV_SID2": "FL1A", "CVLAG": 1.23 }, "geometry": { "type": "Point", "coordinates": [ 647561.747400719323196, 242338.454312447051052 ] } }, +{ "type": "Feature", "properties": { "cat": 40, "ID": 25, "LABEL": "Bay Leaf #1", "LOCATION": "11713 Six Forks Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 2, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 2, "MUNCOID": 2, "BLDGCODE": "279", "AGENCY": "FD", "STATIONID": "BL1A", "RECNO": 42.0, "CV_SID2": "BL1A", "CVLAG": 1.73 }, "geometry": { "type": "Point", "coordinates": [ 641872.115468572126701, 243609.804907889280003 ] } }, +{ "type": "Feature", "properties": { "cat": 41, "ID": 26, "LABEL": "Stony Hill #1", "LOCATION": "7025 Stony Hill Rd", "CITY": "Wake Forest", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "165", "AGENCY": "FD", "STATIONID": "SH1A", "RECNO": 43.0, "CV_SID2": "SH1A", "CVLAG": 1.32 }, "geometry": { "type": "Point", "coordinates": [ 644713.998716329457238, 247773.011927870160434 ] } }, +{ "type": "Feature", "properties": { "cat": 42, "ID": 39, "LABEL": "Stony Hill #2", "LOCATION": "15633 New Light Rd", "CITY": "Wake Forest", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 1, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "280", "AGENCY": "FD", "STATIONID": "SH2A", "RECNO": 44.0, "CV_SID2": "SH2A", "CVLAG": 1.32 }, "geometry": { "type": "Point", "coordinates": [ 641826.335723952855915, 253490.831664361146977 ] } }, +{ "type": "Feature", "properties": { "cat": 43, "ID": 35, "LABEL": "Bay Leaf #2", "LOCATION": "13116 Norwood Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 2, "WATER_RESC": 2, "MUNCOID": 2, "BLDGCODE": "281", "AGENCY": "FD", "STATIONID": "BL2A", "RECNO": 45.0, "CV_SID2": "BL2A", "CVLAG": 1.73 }, "geometry": { "type": "Point", "coordinates": [ 638057.989554733270779, 243294.057771805586526 ] } }, +{ "type": "Feature", "properties": { "cat": 44, "ID": 16, "LABEL": "Durham Highway #1", "LOCATION": "11905 Norwood Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 1, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "282", "AGENCY": "FD", "STATIONID": "DH1A", "RECNO": 46.0, "CV_SID2": "DH1A", "CVLAG": 1.47 }, "geometry": { "type": "Point", "coordinates": [ 634780.300541774719022, 241887.130148449505214 ] } }, +{ "type": "Feature", "properties": { "cat": 45, "ID": 0, "LABEL": "RFD #18", "LOCATION": "8200 Morgans Way", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "283", "AGENCY": "RF", "STATIONID": "RF18", "RECNO": 47.0, "CV_SID2": "RF18", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 638411.819034971646033, 237906.63698060659226 ] } }, +{ "type": "Feature", "properties": { "cat": 46, "ID": 12, "LABEL": "Bay Leaf #3", "LOCATION": "1431 Lynn Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 2, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "284", "AGENCY": "FD", "STATIONID": "BL3A", "RECNO": 48.0, "CV_SID2": "BL3A", "CVLAG": 1.73 }, "geometry": { "type": "Point", "coordinates": [ 640083.49079146864824, 235306.236029645457165 ] } }, +{ "type": "Feature", "properties": { "cat": 47, "ID": 0, "LABEL": "RFD #9", "LOCATION": "4465 Six Forks Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "285", "AGENCY": "RF", "STATIONID": "RF09", "RECNO": 49.0, "CV_SID2": "RF09", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 641827.733222556416877, 232043.346144563838607 ] } }, +{ "type": "Feature", "properties": { "cat": 48, "ID": 0, "LABEL": "RFD #4", "LOCATION": "121 Northway Ct", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "286", "AGENCY": "RF", "STATIONID": "RF04", "RECNO": 50.0, "CV_SID2": "RF04", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 641353.678131233667955, 236615.541157813422615 ] } }, +{ "type": "Feature", "properties": { "cat": 49, "ID": 0, "LABEL": "RFD #11", "LOCATION": "2925 Glenridge Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "287", "AGENCY": "RF", "STATIONID": "RF11", "RECNO": 51.0, "CV_SID2": "RF11", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 645693.136957322480157, 229668.00770110153826 ] } }, +{ "type": "Feature", "properties": { "cat": 50, "ID": 28, "LABEL": "Wake-New Hope #1", "LOCATION": "4615 St. James Church Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 1, "PUMPER_TAN": 1, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 2, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "288", "AGENCY": "FD", "STATIONID": "NH1A", "RECNO": 52.0, "CV_SID2": "NH1A", "CVLAG": 1.63 }, "geometry": { "type": "Point", "coordinates": [ 647962.024397382861935, 232069.597270218975609 ] } }, +{ "type": "Feature", "properties": { "cat": 51, "ID": 0, "LABEL": "RFD #19", "LOCATION": "4209 Spring Forest Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "289", "AGENCY": "RF", "STATIONID": "RF19", "RECNO": 53.0, "CV_SID2": "RF19", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 648353.106461857561953, 233754.550647453172132 ] } }, +{ "type": "Feature", "properties": { "cat": 52, "ID": 27, "LABEL": "Swift Creek", "LOCATION": "6000 Holly Springs Rd", "CITY": "Raleigh", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 3, "TANKER": 2, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 1, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "290", "AGENCY": "FD", "STATIONID": "SC1A", "RECNO": 54.0, "CV_SID2": "SC1A", "CVLAG": 1.32 }, "geometry": { "type": "Point", "coordinates": [ 633178.154770000721328, 221353.037231787107885 ] } }, +{ "type": "Feature", "properties": { "cat": 53, "ID": 333, "LABEL": "Cary #3", "LOCATION": "1807 Kildaire Farm Rd", "CITY": "Cary", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 1, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 2, "MUNCOID": 1, "BLDGCODE": "291", "AGENCY": "CF", "STATIONID": "CF3A", "RECNO": 55.0, "CV_SID2": "CF3A", "CVLAG": 1.0 }, "geometry": { "type": "Point", "coordinates": [ 629523.855567003949545, 220943.518485559878172 ] } }, +{ "type": "Feature", "properties": { "cat": 54, "ID": 0, "LABEL": "RFD #15", "LOCATION": "1815 Spring Forest Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "292", "AGENCY": "RF", "STATIONID": "RF15", "RECNO": 56.0, "CV_SID2": "RF15", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 645010.20365640707314, 234889.916123542498099 ] } }, +{ "type": "Feature", "properties": { "cat": 55, "ID": 0, "LABEL": "RFD #16", "LOCATION": "5225 Lead Mine Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "293", "AGENCY": "RF", "STATIONID": "RF16", "RECNO": 57.0, "CV_SID2": "RF16", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 639120.245176144177094, 233302.515639664168702 ] } }, +{ "type": "Feature", "properties": { "cat": 56, "ID": 0, "LABEL": "RFD #1", "LOCATION": "220 S. Dawson St", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "294", "AGENCY": "RF", "STATIONID": "RF01", "RECNO": 58.0, "CV_SID2": "RF01", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 641803.173608614713885, 224986.142365778650856 ] } }, +{ "type": "Feature", "properties": { "cat": 57, "ID": 0, "LABEL": "Garner #3", "LOCATION": "1695 Timber Dr", "CITY": "Garner", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 2, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "295", "AGENCY": "FD", "STATIONID": "GF3A", "RECNO": 59.0, "CV_SID2": "GF3A", "CVLAG": 1.17 }, "geometry": { "type": "Point", "coordinates": [ 642559.95242120733019, 215531.803534926322754 ] } }, +{ "type": "Feature", "properties": { "cat": 58, "ID": 0, "LABEL": "RFD #21", "LOCATION": "2651 Southall Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 1, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 1, "MUNCOID": 1, "BLDGCODE": "296", "AGENCY": "RF", "STATIONID": "RF21", "RECNO": 60.0, "CV_SID2": "RF21", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 649710.187627459759824, 228717.259671564563178 ] } }, +{ "type": "Feature", "properties": { "cat": 59, "ID": 0, "LABEL": "RFD #22", "LOCATION": "9350 Durant Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "297", "AGENCY": "RF", "STATIONID": "RF22", "RECNO": 61.0, "CV_SID2": "RF22", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 648370.071517499280162, 238362.678775289852638 ] } }, +{ "type": "Feature", "properties": { "cat": 60, "ID": 42, "LABEL": "Morrisville #2", "LOCATION": "10632 Chapel Hill Rd", "CITY": "Morrisville", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 1, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 1, "BRUSH": 0, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "298", "AGENCY": "FD", "STATIONID": "MF2A", "RECNO": 62.0, "CV_SID2": "MF2A", "CVLAG": 1.4 }, "geometry": { "type": "Point", "coordinates": [ 624469.14790387568064, 232478.079323734564241 ] } }, +{ "type": "Feature", "properties": { "cat": 61, "ID": 666, "LABEL": "Cary #6", "LOCATION": "3609 Ten Ten Rd", "CITY": "Cary", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "299", "AGENCY": "CF", "STATIONID": "CF6A", "RECNO": 63.0, "CV_SID2": "CF6A", "CVLAG": 1.0 }, "geometry": { "type": "Point", "coordinates": [ 628787.15813050349243, 216617.029788617015583 ] } }, +{ "type": "Feature", "properties": { "cat": 62, "ID": 0, "LABEL": "Wake Forest #2", "LOCATION": "9925 Ligon Mill Rd", "CITY": "Wake Forest", "MUN_COUNT": "C", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 2, "BLDGCODE": "300", "AGENCY": "FD", "STATIONID": "WF2A", "RECNO": 64.0, "CV_SID2": "WF2A", "CVLAG": 1.48 }, "geometry": { "type": "Point", "coordinates": [ 651703.073228706489317, 243101.080146834981861 ] } }, +{ "type": "Feature", "properties": { "cat": 63, "ID": 0, "LABEL": "RFD #24", "LOCATION": "10440 Fossil Creek Ct", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": null, "AGENCY": "RF", "STATIONID": "RF24", "RECNO": 65.0, "CV_SID2": "RF24", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 628006.344838843913749, 238736.101014297950314 ] } }, +{ "type": "Feature", "properties": { "cat": 64, "ID": 0, "LABEL": "RFD #25", "LOCATION": "2740 Wakefield Crossing", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": null, "AGENCY": "RF", "STATIONID": "RF25", "RECNO": 66.0, "CV_SID2": "RF25", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 649425.628821200225502, 243030.494216773979133 ] } }, +{ "type": "Feature", "properties": { "cat": 65, "ID": 0, "LABEL": "Willow Springs #1", "LOCATION": "2474 Bud Lipscomb Rd", "CITY": "Fuquay-Varina", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": "301", "AGENCY": "FD", "STATIONID": "FV3A", "RECNO": 67.0, "CV_SID2": "FV3A", "CVLAG": 1.33 }, "geometry": { "type": "Point", "coordinates": [ 636934.54128505603876, 200696.26843876123894 ] } }, +{ "type": "Feature", "properties": { "cat": 66, "ID": 0, "LABEL": "Knightdale Public Safety", "LOCATION": "306 Robertson Rd", "CITY": "Knightdale", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": null, "AGENCY": "KC", "STATIONID": "KC1A", "RECNO": 68.0, "CV_SID2": "KC1A", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 657091.418194862431847, 226364.964583369815955 ] } }, +{ "type": "Feature", "properties": { "cat": 67, "ID": 0, "LABEL": "RFD #27", "LOCATION": "5916 Buffaloe Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": null, "AGENCY": "RF", "STATIONID": "RF27", "RECNO": 69.0, "CV_SID2": "RF27", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 651050.345248824334703, 232036.687777677958366 ] } }, +{ "type": "Feature", "properties": { "cat": 68, "ID": 0, "LABEL": "RFD #26", "LOCATION": "3929 Barwell Rd", "CITY": "Raleigh", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": null, "AGENCY": "RF", "STATIONID": "RF26", "RECNO": 70.0, "CV_SID2": "RF26", "CVLAG": 0.0 }, "geometry": { "type": "Point", "coordinates": [ 649833.805937557597645, 219907.2934799711511 ] } }, +{ "type": "Feature", "properties": { "cat": 69, "ID": 0, "LABEL": "Apex #3", "LOCATION": null, "CITY": "Apex", "MUN_COUNT": "M", "PUMPERS": 0, "PUMPER_TAN": 0, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 0, "MUNCOID": 0, "BLDGCODE": null, "AGENCY": "FD", "STATIONID": "AF3A", "RECNO": 0.0, "CV_SID2": "AF3A", "CVLAG": 1.4 }, "geometry": { "type": "Point", "coordinates": [ 622136.908598274341784, 220306.429225125611993 ] } }, +{ "type": "Feature", "properties": { "cat": 70, "ID": 37, "LABEL": "Holly Springs Sta #1", "LOCATION": "700 Flint Point Ln", "CITY": "Holly Springs", "MUN_COUNT": "M", "PUMPERS": 1, "PUMPER_TAN": 1, "TANKER": 0, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 0, "OTHERS": 0, "WATER_RESC": 4, "MUNCOID": 1, "BLDGCODE": "244", "AGENCY": "FD", "STATIONID": "HS1", "RECNO": 5.0, "CV_SID2": "HS1A", "CVLAG": 1.37 }, "geometry": { "type": "Point", "coordinates": [ 625889.87838139932137, 211478.01839282058063 ] } }, +{ "type": "Feature", "properties": { "cat": 71, "ID": 14, "LABEL": "Holly Springs Sta #2", "LOCATION": "1140 Avent Ferry Rd", "CITY": "Holly Springs", "MUN_COUNT": "M", "PUMPERS": 2, "PUMPER_TAN": 0, "TANKER": 1, "MINI_PUMPE": 0.0, "RESCUE_SER": 0, "AERIAL": 0, "BRUSH": 1, "OTHERS": 1, "WATER_RESC": 0, "MUNCOID": 1, "BLDGCODE": "245", "AGENCY": "FD", "STATIONID": "HS2", "RECNO": 6.0, "CV_SID2": "HS2A", "CVLAG": 1.37 }, "geometry": { "type": "Point", "coordinates": [ 623658.643345945398323, 209478.243302924296586 ] } } +] +} diff --git a/tests/mock/actinia_raster_management_mock.py b/tests/mock/actinia_raster_management_mock.py new file mode 100644 index 0000000..54ebe83 --- /dev/null +++ b/tests/mock/actinia_raster_management_mock.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# actinia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "Anika Weinmann" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "Anika Weinmann" + +from .actinia_mock import ( + ACTINIA_BASEURL, + ACTINIA_VERSION +) + + +get_rasters_mock = { + "accept_datetime": "2022-06-13 13:04:53.422064", + "accept_timestamp": 1655125493.4220603, + "api_info": { + "endpoint": "rasterlayersresource", + "method": "GET", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/" + "PERMANENT/raster_layers", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/PERMANENT/raster_layers", + }, + "datetime": "2022-06-13 13:04:53.740702", + "http_code": 200, + "message": "Processing successfully finished", + "process_chain_list": [ + { + "1": { + "inputs": {"mapset": "PERMANENT", "type": "raster"}, + "module": "g.list", + } + } + ], + "process_log": [ + { + "executable": "g.list", + "id": "1", + "parameter": ["mapset=PERMANENT", "type=raster"], + "return_code": 0, + "run_time": 0.10059475898742676, + "stderr": [""], + "stdout": "aspect\nbasin_50K\nboundary_county_500m\ncfactorbare_" + "1m\ncfactorgrow_1m\nel_D782_6m\nel_D783_6m\nel_D792_6m\nel_D793_" + "6m\nelev_lid792_1m\nelev_ned_30m\nelev_srtm_30m\nelev_state_500" + "m\nelevation\nelevation_shade\nfacility\ngeology_30m\nlakes" + "\nlandclass96\nlandcover_1m\nlanduse96_28m\nlsat7_2002_10\n" + "lsat7_2002_20\nlsat7_2002_30\nlsat7_2002_40\nlsat7_2002_50\n" + "lsat7_2002_61\nlsat7_2002_62\nlsat7_2002_70\nlsat7_2002_80" + "\nncmask_500m\northo_2001_t792_1m\nroadsmajor\nslope\nsoilsID\n" + "soils_Kfactor\nstreams_derived\ntowns\nurban\nzipcodes\n" + "zipcodes_dbl\n", + } + ], + "process_results": [ + "aspect", + "basin_50K", + "boundary_county_500m", + "cfactorbare_1m", + "cfactorgrow_1m", + "el_D782_6m", + "el_D783_6m", + "el_D792_6m", + "el_D793_6m", + "elev_lid792_1m", + "elev_ned_30m", + "elev_srtm_30m", + "elev_state_500m", + "elevation", + "elevation_shade", + "facility", + "geology_30m", + "lakes", + "landclass96", + "landcover_1m", + "landuse96_28m", + "lsat7_2002_10", + "lsat7_2002_20", + "lsat7_2002_30", + "lsat7_2002_40", + "lsat7_2002_50", + "lsat7_2002_61", + "lsat7_2002_62", + "lsat7_2002_70", + "lsat7_2002_80", + "ncmask_500m", + "ortho_2001_t792_1m", + "roadsmajor", + "slope", + "soilsID", + "soils_Kfactor", + "streams_derived", + "towns", + "urban", + "zipcodes", + "zipcodes_dbl", + ], + "progress": {"num_of_steps": 1, "step": 1}, + "resource_id": "resource_id-0a7cb1ed-4fab-4353-9aac-87bb292c5a48", + "status": "finished", + "time_delta": 0.31871747970581055, + "timestamp": 1655125493.7406285, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-0a7cb1ed-4fab-4353-9aac-87bb292c5a48", + }, + "user_id": "testuser", +} + +raster_info_resp = { + "cells": "2025000", + "cols": "1500", + "comments": '"int(zipcodes)"', + "creator": '"helena"', + "database": "/actinia_core/workspace/temp_db/" + "gisdbase_d6bc2ddef1fd4471a84261ea5b108417", + "datatype": "CELL", + "date": '"Sun Jan 14 22:36:04 2007"', + "description": '"generated by r.mapcalc"', + "east": "645000", + "ewres": "10", + "location": "nc_spm_08", + "map": "zipcodes", + "mapset": "PERMANENT", + "maptype": "raster", + "max": "27610", + "min": "27511", + "ncats": "27610", + "north": "228500", + "nsres": "10", + "rows": "1350", + "semantic_label": '"none"', + "source1": '""', + "source2": '""', + "south": "215000", + "timestamp": '"none"', + "title": '"South West Wake: Zipcode areas derived from vector map"', + "units": '"none"', + "vdatum": '"none"', + "west": "630000", +} + + +get_raster_info_mock = { + "accept_datetime": "2022-06-13 13:28:23.934771", + "accept_timestamp": 1655126903.934769, + "api_info": { + "endpoint": "rasterlayerresource", + "method": "GET", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/PERMANENT" + "/raster_layers/zipcodes", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/PERMANENT/raster_layers/zipcodes", + }, + "datetime": "2022-06-13 13:28:24.685413", + "http_code": 200, + "message": "Processing successfully finished", + "process_chain_list": [ + { + "1": { + "flags": "gre", + "inputs": {"map": "zipcodes@PERMANENT"}, + "module": "r.info", + } + } + ], + "process_log": [ + { + "executable": "r.info", + "id": "1", + "mapset_size": 421, + "parameter": ["map=zipcodes@PERMANENT", "-gre"], + "return_code": 0, + "run_time": 0.1002960205078125, + "stderr": [""], + "stdout": "north=228500\nsouth=215000\neast=645000\nwest=630000" + "\nnsres=10\newres=10\nrows=1350\ncols=1500\ncells=2025000\n" + "datatype=CELL\nncats=27610\nmin=27511\nmax=27610\nmap=zipcodes\n" + "maptype=raster\nmapset=PERMANENT\nlocation=nc_spm_08\ndatabase" + "=/actinia_core/workspace/temp_db/gisdbase_d6bc2ddef1fd4471a8426" + '1ea5b108417\ndate="Sun Jan 14 22:36:04 2007"\ncreator="helena"\n' + 'title="South West Wake: Zipcode areas derived from vector map"\n' + 'timestamp="none"\nunits="none"\nvdatum="none"\nsemantic_label=' + '"none"\nsource1=""\nsource2=""\ndescription="generated by ' + 'r.mapcalc"\ncomments="int(zipcodes)"\n', + } + ], + "process_results": raster_info_resp, + "progress": {"num_of_steps": 1, "step": 1}, + "resource_id": "resource_id-165e9eca-9ab1-488b-8f49-d8b61f02315c", + "status": "finished", + "time_delta": 0.7506742477416992, + "timestamp": 1655126904.6853848, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-165e9eca-9ab1-488b-8f49-d8b61f02315c", + }, + "user_id": "testuser", +} + +upload_raster_resp = { + "accept_datetime": "2022-06-13 17:15:38.438675", + "accept_timestamp": 1655140538.4386735, + "api_info": { + "endpoint": "rasterlayerresource", + "method": "POST", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/" + "raster_upload/raster_layers/test_elevation", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/raster_upload/raster_layers/" + "test_elevation", + }, + "datetime": "2022-06-13 17:15:41.866644", + "http_code": 200, + "message": "Raster layer successfully imported.", + "process_chain_list": [ + { + "1": { + "inputs": { + "mapset": "raster_upload", + "pattern": "test_elevation", + "type": "raster", + }, + "module": "g.list", + } + }, + { + "1": { + "inputs": { + "input": "/actinia_core/workspace/download_cache/testuser/" + "home_testuser_data_tif_elevation_8e03bc94-58fc-" + "449d-a4d2-4f4a2ac326a6.tif", + "output": "test_elevation", + }, + "module": "r.import", + } + }, + ], + "process_log": [ + { + "executable": "g.list", + "id": "1", + "mapset_size": 489, + "parameter": [ + "type=raster", + "pattern=test_elevation", + "mapset=raster_upload", + ], + "return_code": 0, + "run_time": 0.10027885437011719, + "stderr": [""], + "stdout": "", + }, + { + "executable": "r.import", + "id": "1", + "mapset_size": 36527, + "parameter": [ + "input=/actinia_core/workspace/download_cache/testuser/" + "home_testuser_data_tif_elevation_8e03bc94-58fc-449d-a4d2-" + "4f4a2ac326a6.tif", + "output=test_elevation", + ], + "return_code": 0, + "run_time": 0.9039969444274902, + "stderr": [ + "Importing raster map ...", + "0..3..6..9..12..15..18..21..24..27..30..33..36..39..42..45.." + "48..51..54..57..60..63..66..69..72..75..78..81..84..87..90.." + "93..96..99..100", + "", + ], + "stdout": "", + }, + ], + "process_results": {}, + "progress": {"num_of_steps": 2, "step": 2}, + "resource_id": "resource_id-512baa14-253b-4030-95f7-e6bd2aec7493", + "status": "finished", + "time_delta": 3.427985668182373, + "timestamp": 1655140541.8666322, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-512baa14-253b-4030-95f7-e6bd2aec7493", + }, + "user_id": "testuser", +} + +start_job_resp = { + "accept_datetime": "2022-06-13 17:15:38.438675", + "accept_timestamp": 1655140538.4386735, + "api_info": { + "endpoint": "rasterlayerresource", + "method": "POST", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/raster_" + "upload/raster_layers/test_elevation", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/raster_upload/raster_layers/" + "test_elevation", + }, + "datetime": "2022-06-13 17:15:38.446568", + "http_code": 200, + "message": "Resource accepted", + "process_chain_list": [], + "process_results": {}, + "resource_id": "resource_id-512baa14-253b-4030-95f7-e6bd2aec7493", + "status": "accepted", + "time_delta": 0.007901191711425781, + "timestamp": 1655140538.4465668, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-512baa14-253b-4030-95f7-e6bd2aec7493", + }, + "user_id": "testuser", +} + +delete_raster_resp = { + "accept_datetime": "2022-06-13 16:47:11.782701", + "accept_timestamp": 1655138831.7826996, + "api_info": { + "endpoint": "rasterlayerresource", + "method": "DELETE", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/" + "raster_upload/raster_layers/test_elevation", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/raster_upload/raster_layers/" + "test_elevation", + }, + "datetime": "2022-06-13 16:47:12.590450", + "http_code": 200, + "message": "Raster layer successfully removed.", + "process_chain_list": [ + { + "1": { + "flags": "f", + "inputs": {"name": "test_elevation", "type": "raster"}, + "module": "g.remove", + } + } + ], + "process_log": [ + { + "executable": "g.remove", + "id": "1", + "parameter": ["type=raster", "name=test_elevation", "-f"], + "return_code": 0, + "run_time": 0.35126757621765137, + "stderr": ["Removing raster ", ""], + "stdout": "", + } + ], + "process_results": {}, + "progress": {"num_of_steps": 1, "step": 1}, + "resource_id": "resource_id-3f4ce1c1-8083-4658-ba0a-5af2e08c7b04", + "status": "finished", + "time_delta": 0.8077754974365234, + "timestamp": 1655138832.5904214, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-3f4ce1c1-8083-4658-ba0a-5af2e08c7b04", + }, + "user_id": "testuser", +} diff --git a/tests/mock/actinia_vector_management_mock.py b/tests/mock/actinia_vector_management_mock.py new file mode 100644 index 0000000..114eda4 --- /dev/null +++ b/tests/mock/actinia_vector_management_mock.py @@ -0,0 +1,527 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# acintia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "testuser Weinmann" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "testuser Weinmann" + +from .actinia_mock import ( + ACTINIA_BASEURL, + ACTINIA_VERSION, +) + +get_vectors_mock = { + "accept_datetime": "2022-06-14 09:57:39.460876", + "accept_timestamp": 1655200659.4608743, + "api_info": { + "endpoint": "vectorlayersresource", + "method": "GET", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/PERMANENT" + "/vector_layers", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/PERMANENT/vector_layers", + }, + "datetime": "2022-06-14 09:57:39.817329", + "http_code": 200, + "message": "Processing successfully finished", + "process_chain_list": [ + { + "1": { + "inputs": {"mapset": "PERMANENT", "type": "vector"}, + "module": "g.list", + } + } + ], + "process_log": [ + { + "executable": "g.list", + "id": "1", + "parameter": ["mapset=PERMANENT", "type=vector"], + "return_code": 0, + "run_time": 0.10045146942138672, + "stderr": [""], + "stdout": "P079214\nP079215\nP079218\nP079219\nboundary_county\n" + "boundary_municp\nbridges\nbusroute1\nbusroute11\nbusroute6\n" + "busroute_a\nbusroutesall\nbusstopsall\ncensus_wake2000\n" + "censusblk_swwake\ncomm_colleges\nelev_lid792_bepts\nelev_lid792" + "_cont1m\nelev_lid792_randpts\nelev_lidrural_mrpts\nelev_lidrural" + "_mrptsft\nelev_ned10m_cont10m\nfirestations\ngeodetic_pts\n" + "geodetic_swwake_pts\ngeology\ngeonames_NC\ngeonames_wake\n" + "hospitals\nlakes\nnc_state\noverpasses\npoi_names_wake\n" + "precip_30ynormals\nprecip_30ynormals_3d\nrailroads\nroadsmajor" + "\nschools_wake\nsoils_general\nsoils_wake\nstreams\nstreets_wake" + "\nswwake_10m\nurbanarea\nusgsgages\nzipcodes_wake\n", + } + ], + "process_results": [ + "P079214", + "P079215", + "P079218", + "P079219", + "boundary_county", + "boundary_municp", + "bridges", + "busroute1", + "busroute11", + "busroute6", + "busroute_a", + "busroutesall", + "busstopsall", + "census_wake2000", + "censusblk_swwake", + "comm_colleges", + "elev_lid792_bepts", + "elev_lid792_cont1m", + "elev_lid792_randpts", + "elev_lidrural_mrpts", + "elev_lidrural_mrptsft", + "elev_ned10m_cont10m", + "firestations", + "geodetic_pts", + "geodetic_swwake_pts", + "geology", + "geonames_NC", + "geonames_wake", + "hospitals", + "lakes", + "nc_state", + "overpasses", + "poi_names_wake", + "precip_30ynormals", + "precip_30ynormals_3d", + "railroads", + "roadsmajor", + "schools_wake", + "soils_general", + "soils_wake", + "streams", + "streets_wake", + "swwake_10m", + "urbanarea", + "usgsgages", + "zipcodes_wake", + ], + "progress": {"num_of_steps": 1, "step": 1}, + "resource_id": "resource_id-2a958ad5-020c-4b3a-a748-d43e11698914", + "status": "finished", + "time_delta": 0.35648655891418457, + "timestamp": 1655200659.8172982, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-2a958ad5-020c-4b3a-a748-d43e11698914", + }, + "user_id": "testuser", +} + +vector_info_resp = { + "Attributes": [ + {"column": "cat", "type": "INTEGER"}, + {"column": "AREA", "type": "DOUBLE PRECISION"}, + {"column": "PERIMETER", "type": "DOUBLE PRECISION"}, + {"column": "FIPS", "type": "DOUBLE PRECISION"}, + {"column": "NAME", "type": "CHARACTER"}, + {"column": "NAME_LOCAS", "type": "CHARACTER"}, + {"column": "DOT_DISTRI", "type": "INTEGER"}, + {"column": "DOT_DIVISI", "type": "INTEGER"}, + {"column": "DOT_COUNTY", "type": "INTEGER"}, + {"column": "COUNTY_100", "type": "INTEGER"}, + {"column": "DOT_GROUP_", "type": "CHARACTER"}, + {"column": "ACRES", "type": "DOUBLE PRECISION"}, + {"column": "ABBR_5CHAR", "type": "CHARACTER"}, + {"column": "ABBR_4CHAR", "type": "CHARACTER"}, + {"column": "ABBR_2CHAR", "type": "CHARACTER"}, + {"column": "Z_MEAN", "type": "DOUBLE PRECISION"}, + {"column": "Z_MIN", "type": "DOUBLE PRECISION"}, + {"column": "Z_MAX", "type": "DOUBLE PRECISION"}, + {"column": "Z_ZONE", "type": "DOUBLE PRECISION"}, + {"column": "CO_CENSUS", "type": "CHARACTER"}, + {"column": "DIV_CONTAC", "type": "CHARACTER"}, + {"column": "DIST_CONTA", "type": "CHARACTER"}, + {"column": "CO_WIKIPED", "type": "CHARACTER"}, + {"column": "Shape_Leng", "type": "DOUBLE PRECISION"}, + {"column": "Shape_Area", "type": "DOUBLE PRECISION"}, + ], + "COMMAND": ' v.db.connect -o map="boundary_county@PERMANENT" driver=' + '"sqlite" database="$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/' + 'sqlite.db" table="boundary_county" key="cat" layer="1" ' + 'separator="|"', + "areas": "926", + "attribute_database": "/actinia_core/workspace/temp_db/gisdbase_6e4ca293" + "5544442e9b68954aae25f809/nc_spm_08/PERMANENT/sqlite" + "/sqlite.db", + "attribute_database_driver": "sqlite", + "attribute_layer_name": "boundary_county", + "attribute_layer_number": "1", + "attribute_primary_key": "cat", + "attribute_table": "boundary_county", + "bottom": "0.000000", + "boundaries": "1910", + "centroids": "926", + "comment": "", + "creator": "helena", + "database": "/actinia_core/workspace/temp_db/gisdbase_6e4ca2935544442e9b6" + "8954aae25f809", + "digitization_threshold": "0.000000", + "east": "962679.95935005", + "format": "native", + "islands": "130", + "level": "2", + "lines": "0", + "location": "nc_spm_08", + "map3d": "0", + "mapset": "PERMANENT", + "name": "boundary_county", + "nodes": "1114", + "north": "318097.688745074", + "num_dblinks": "1", + "organization": "NC OneMap", + "points": "0", + "primitives": "2836", + "projection": "Lambert Conformal Conic", + "scale": "1:1", + "source_date": "Tue Apr 3 13:23:49 2007", + "south": "-15865.3468644768", + "timestamp": "none", + "title": "North Carolina county boundaries (polygon map)", + "top": "0.000000", + "west": "124002.67019024", +} + +get_vector_info_mock = { + "accept_datetime": "2022-06-14 09:58:37.867650", + "accept_timestamp": 1655200717.8676484, + "api_info": { + "endpoint": "vectorlayerresource", + "method": "GET", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/PERMANENT" + "/vector_layers/boundary_county", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/PERMANENT/vector_layers/" + "boundary_county", + }, + "datetime": "2022-06-14 09:58:39.624698", + "http_code": 200, + "message": "Processing successfully finished", + "process_chain_list": [ + { + "1": { + "flags": "gte", + "inputs": {"map": "boundary_county@PERMANENT"}, + "module": "v.info", + }, + "2": { + "flags": "h", + "inputs": {"map": "boundary_county@PERMANENT"}, + "module": "v.info", + }, + "3": { + "flags": "c", + "inputs": {"map": "boundary_county@PERMANENT"}, + "module": "v.info", + }, + } + ], + "process_log": [ + { + "executable": "v.info", + "id": "1", + "mapset_size": 421, + "parameter": ["map=boundary_county@PERMANENT", "-gte"], + "return_code": 0, + "run_time": 0.30083537101745605, + "stderr": [""], + "stdout": "name=boundary_county\nmapset=PERMANENT\nlocation=" + "nc_spm_08\ndatabase=/actinia_core/workspace/temp_db/" + "gisdbase_6e4ca2935544442e9b68954aae25f809\ntitle=North " + "Carolina county boundaries (polygon map)\nscale=1:1\n" + "creator=helena\norganization=NC OneMap\nsource_date=" + "Tue Apr 3 13:23:49 2007\ntimestamp=none\nformat=native" + "\nlevel=2\nnum_dblinks=1\nattribute_layer_number=1\n" + "attribute_layer_name=boundary_county\nattribute_" + "database=/actinia_core/workspace/temp_db/gisdbase_6e4ca" + "2935544442e9b68954aae25f809/nc_spm_08/PERMANENT/sqlite/" + "sqlite.db\nattribute_database_driver=sqlite\nattribute" + "_table=boundary_county\nattribute_primary_key=cat\n" + "projection=Lambert Conformal Conic\ndigitization_thres" + "hold=0.000000\ncomment=\nnorth=318097.688745074\nsouth" + "=-15865.3468644768\neast=962679.95935005\nwest=124002." + "67019024\ntop=0.000000\nbottom=0.000000\nnodes=1114\n" + "points=0\nlines=0\nboundaries=1910\ncentroids=926\n" + "areas=926\nislands=130\nprimitives=2836\nmap3d=0\n", + }, + { + "executable": "v.info", + "id": "2", + "mapset_size": 421, + "parameter": ["map=boundary_county@PERMANENT", "-h"], + "return_code": 0, + "run_time": 0.20082664489746094, + "stderr": [""], + "stdout": 'COMMAND: v.in.ogr dsn="CountyBoundaryShoreline.shp" ' + 'output="boundary_county" min_area=0.0001 snap=-1\nGISD' + 'BASE: /bigdata/bakncgrassdata\nLOCATION: nc_spm_03 ' + 'MAPSET: user1 USER: helena DATE: Tue Apr 3 13:23:49 ' + '2007\n-------------------------------------------------' + '--------------------------------\n926 input polygons\n' + 'total area: 1.541189e+11 (926 areas)\noverlapping area:' + ' 0.000000e+00 (0 areas)\narea without category: ' + '0.000000e+00 (0 areas)\n-------------------------------' + '--------------------------------------------------\n---' + '-------------------------------------------------------' + '-----------------------\nCOMMAND: v.db.connect -o map=' + '"boundary_county@PERMANENT" driver="sqlite" database=' + '"/home/neteler/grassdata/nc_spm_latest/nc_spm_08/' + 'PERMANENT/sqlite/sqlite.db" table="boundary_county" ' + 'key="cat" layer="1" separator="|"\nGISDBASE: /home/' + 'neteler/grassdata/nc_spm_latest\nLOCATION: nc_spm_08 ' + 'MAPSET: PERMANENT USER: neteler DATE: Mon Nov 26 16:55:' + '29 2012\n----------------------------------------------' + '-----------------------------------\nCOMMAND: ' + 'v.db.connect -o map="boundary_county@PERMANENT" driver=' + '"sqlite" database="$GISDBASE/$LOCATION_NAME/$MAPSET/' + 'sqlite/sqlite.db" table="boundary_county" key="cat" ' + 'layer="1" separator="|"\nGISDBASE: /home/neteler/' + 'grassdata\nLOCATION: nc_spm_08_grass7 MAPSET: PERMANENT' + ' USER: neteler DATE: Fri Dec 7 23:25:11 2012\n', + }, + { + "executable": "v.info", + "id": "3", + "mapset_size": 421, + "parameter": ["map=boundary_county@PERMANENT", "-c"], + "return_code": 0, + "run_time": 0.2508242130279541, + "stderr": [ + "Displaying column types/names for database connection of " + "layer <1>:", + "", + ], + "stdout": "INTEGER|cat\nDOUBLE PRECISION|AREA\nDOUBLE PRECISION|" + "PERIMETER\nDOUBLE PRECISION|FIPS\nCHARACTER|NAME\n" + "CHARACTER|NAME_LOCAS\nINTEGER|DOT_DISTRI\nINTEGER|" + "DOT_DIVISI\nINTEGER|DOT_COUNTY\nINTEGER|COUNTY_100\n" + "CHARACTER|DOT_GROUP_\nDOUBLE PRECISION|ACRES\nCHARACTER" + "|ABBR_5CHAR\nCHARACTER|ABBR_4CHAR\nCHARACTER|" + "ABBR_2CHAR\nDOUBLE PRECISION|Z_MEAN\nDOUBLE PRECISION|" + "Z_MIN\nDOUBLE PRECISION|Z_MAX\nDOUBLE PRECISION|Z_ZONE" + "\nCHARACTER|CO_CENSUS\nCHARACTER|DIV_CONTAC\nCHARACTER" + "|DIST_CONTA\nCHARACTER|CO_WIKIPED\nDOUBLE PRECISION|" + "Shape_Leng\nDOUBLE PRECISION|Shape_Area\n", + }, + ], + "process_results": vector_info_resp, + "progress": {"num_of_steps": 3, "step": 3}, + "resource_id": "resource_id-dc63156b-edf8-4295-a1af-4c149f847730", + "status": "finished", + "time_delta": 1.757124423980713, + "timestamp": 1655200719.6246197, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-dc63156b-edf8-4295-a1af-4c149f847730", + }, + "user_id": "testuser", +} + +upload_vector_resp = { + "accept_datetime": "2022-06-14 10:00:30.093043", + "accept_timestamp": 1655200830.0930417, + "api_info": { + "endpoint": "vectorlayerresource", + "method": "POST", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/" + "raster_upload/vector_layers/test_vector", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/raster_upload/vector_layers/" + "test_vector", + }, + "datetime": "2022-06-14 10:00:35.041458", + "http_code": 200, + "message": "Vector layer successfully imported.", + "process_chain_list": [ + { + "1": { + "inputs": { + "mapset": "raster_upload", + "pattern": "test_vector", + "type": "vector", + }, + "module": "g.list", + } + }, + { + "1": { + "inputs": { + "input": "/actinia_core/workspace/download_cache/testuser/" + "unzip_afbddb40-e370-4c7d-b00c-4823f0853d82/" + "firestations.shp" + }, + "module": "v.import", + "outputs": {"output": {"name": "test_vector"}}, + } + }, + ], + "process_log": [ + { + "executable": "g.list", + "id": "1", + "mapset_size": 489, + "parameter": [ + "type=vector", + "pattern=test_vector", + "mapset=raster_upload", + ], + "return_code": 0, + "run_time": 0.10049247741699219, + "stderr": [""], + "stdout": "", + }, + { + "executable": "v.import", + "id": "1", + "mapset_size": 33790, + "parameter": [ + "input=/actinia_core/workspace/download_cache/testuser/" + "unzip_afbddb40-e370-4c7d-b00c-4823f0853d82/firestations.shp", + "output=test_vector", + ], + "return_code": 0, + "run_time": 1.1054775714874268, + "stderr": [ + "Check if OGR layer contains polygons...", + "0..2..4..7..9..11..14..16..18..21..23..25..28..30..32..35.." + "38..40..42..45..47..49..52..54..56..59..61..63..66..69..71.." + "73..76..78..80..83..85..87..90..92..94..97..100", + "Creating attribute table for layer ...", + "Column name renamed to ", + "Importing 71 features (OGR layer )...", + "0..2..4..7..9..11..14..16..18..21..23..25..28..30..32..35.." + "38..40..42..45..47..49..52..54..56..59..61..63..66..69..71.." + "73..76..78..80..83..85..87..90..92..94..97..100", + "-----------------------------------------------------", + "Building topology for vector map ...", + "Registering primitives...", + "", + "Input " + "successfully imported without reprojection", + "", + ], + "stdout": "", + }, + ], + "process_results": {}, + "progress": {"num_of_steps": 2, "step": 2}, + "resource_id": "resource_id-f887fddc-ca92-4e6b-aaaa-169023627567", + "status": "finished", + "time_delta": 4.9484381675720215, + "timestamp": 1655200835.041446, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-f887fddc-ca92-4e6b-aaaa-169023627567", + }, + "user_id": "testuser", +} + +start_job_resp = { + "accept_datetime": "2022-06-14 10:00:30.093043", + "accept_timestamp": 1655200830.0930417, + "api_info": { + "endpoint": "vectorlayerresource", + "method": "POST", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/raster_" + "upload/vector_layers/test_vector", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/raster_upload/vector_layers/" + "test_vector", + }, + "datetime": "2022-06-14 10:00:30.098413", + "http_code": 200, + "message": "Resource accepted", + "process_chain_list": [], + "process_results": {}, + "resource_id": "resource_id-f887fddc-ca92-4e6b-aaaa-169023627567", + "status": "accepted", + "time_delta": 0.005391836166381836, + "timestamp": 1655200830.0984113, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-f887fddc-ca92-4e6b-aaaa-169023627567", + }, + "user_id": "testuser", +} + +delete_vector_resp = { + "accept_datetime": "2022-06-14 10:03:56.402405", + "accept_timestamp": 1655201036.402403, + "api_info": { + "endpoint": "vectorlayerresource", + "method": "DELETE", + "path": f"/api/{ACTINIA_VERSION}/locations/nc_spm_08/mapsets/" + "raster_upload/vector_layers/test_vector", + "request_url": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/locations/" + "nc_spm_08/mapsets/raster_upload/vector_layers/" + "test_vector", + }, + "datetime": "2022-06-14 10:03:56.981348", + "http_code": 200, + "message": "Vector layer successfully removed.", + "process_chain_list": [ + { + "1": { + "flags": "f", + "inputs": {"name": "test_vector", "type": "vector"}, + "module": "g.remove", + } + } + ], + "process_log": [ + { + "executable": "g.remove", + "id": "1", + "parameter": ["type=vector", "name=test_vector", "-f"], + "return_code": 0, + "run_time": 0.4009582996368408, + "stderr": ["Removing vector ", ""], + "stdout": "", + } + ], + "process_results": {}, + "progress": {"num_of_steps": 1, "step": 1}, + "resource_id": "resource_id-c4713ee6-4aad-4831-9d55-ad2c989937da", + "status": "finished", + "time_delta": 0.5789728164672852, + "timestamp": 1655201036.9813201, + "urls": { + "resources": [], + "status": f"{ACTINIA_BASEURL}/api/{ACTINIA_VERSION}/resources/testuser" + "/resource_id-c4713ee6-4aad-4831-9d55-ad2c989937da", + }, + "user_id": "testuser", +} diff --git a/tests/test_actinia_raster_management.py b/tests/test_actinia_raster_management.py new file mode 100644 index 0000000..431f0b4 --- /dev/null +++ b/tests/test_actinia_raster_management.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# actinia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "Anika Weinmann" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "Anika Weinmann" + +import json +import os +from unittest.mock import Mock, patch + +from actinia import Actinia +from actinia.raster import Raster + +from .mock.actinia_mock import ( + ACTINIA_BASEURL, + version_resp_text, + location_get_mapset_resp, +) +from .mock.actinia_location_management_mock import get_locations_resp +from .mock.actinia_raster_management_mock import ( + get_rasters_mock, + get_raster_info_mock, + raster_info_resp, + upload_raster_resp, + delete_raster_resp, + start_job_resp, +) + + +LOCATION_NAME = "nc_spm_08" +MAPSET_NAME = "PERMANENT" +RASTER_NAME = "zipcodes" +UPLOAD_RASTER_TIF = "./data/elevation.tif" +UPLOAD_RASTER_NAME = "test_raster" + + +class TestActiniaRaster(object): + @classmethod + def setup_class(cls): + cls.mock_get_patcher = patch("actinia.actinia.requests.get") + cls.mock_post_patcher = patch("actinia.actinia.requests.post") + cls.mock_delete_patcher = patch("actinia.actinia.requests.delete") + cls.mock_get = cls.mock_get_patcher.start() + cls.mock_post = cls.mock_post_patcher.start() + cls.mock_delete = cls.mock_delete_patcher.start() + + cls.mock_get.return_value = Mock() + cls.mock_get.return_value.status_code = 200 + cls.mock_get.return_value.text = json.dumps(version_resp_text) + cls.testactinia = Actinia(ACTINIA_BASEURL) + assert isinstance(cls.testactinia, Actinia) + cls.testactinia.set_authentication("user", "pw") + # get locations + cls.mock_get.return_value = Mock() + cls.mock_get.return_value.status_code = 200 + cls.mock_get.return_value.text = json.dumps(get_locations_resp) + cls.testactinia.get_locations() + # get mapsets + cls.mock_get.return_value = Mock() + cls.mock_get.return_value.status_code = 200 + cls.mock_get.return_value.text = json.dumps(location_get_mapset_resp) + cls.testactinia.locations[LOCATION_NAME].get_mapsets() + + @classmethod + def teardown_class(cls): + cls.mock_get_patcher.stop() + cls.mock_post_patcher.stop() + cls.mock_delete_patcher.stop() + + def test_get_rasters_and_raster_info(self): + """Test get_raster_layers and get_info methods.""" + # get raster layers + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(get_rasters_mock) + resp = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].get_raster_layers() + + assert isinstance(resp, dict), "response is not a dictionary" + assert "zipcodes" in resp, "'zipcodes' raster not in response" + assert isinstance( + resp["zipcodes"], Raster + ), "Rasters not of type Raster" + assert resp == self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].raster_layers + + # get raster info + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(get_raster_info_mock) + resp = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].raster_layers[RASTER_NAME].get_info() + + raster = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].raster_layers[RASTER_NAME] + assert isinstance(resp, dict), "response is not a dictionary" + assert raster_info_resp == resp, "response is not correct" + assert raster.info == resp, "raster info is not set correctly" + assert raster.region is not None, "raster region is not set" + + def test_upload_and_delete_raster(self): + """Test upload_raster and delete_raster methods.""" + # mockup + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(upload_raster_resp) + self.mock_post.return_value = Mock() + self.mock_post.return_value.status_code = 200 + self.mock_post.return_value.text = json.dumps(start_job_resp) + self.mock_delete.return_value = Mock() + self.mock_delete.return_value.status_code = 200 + self.mock_delete.return_value.text = json.dumps(delete_raster_resp) + + # upload + dir_path = os.path.dirname(os.path.realpath(__file__)) + tif_path = os.path.join(dir_path, UPLOAD_RASTER_TIF) + self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].upload_raster(UPLOAD_RASTER_NAME, tif_path) + raster_layers = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].raster_layers + assert UPLOAD_RASTER_NAME in raster_layers + + # delete + self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].delete_raster(UPLOAD_RASTER_NAME) + raster_layers = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].raster_layers + assert UPLOAD_RASTER_NAME not in raster_layers diff --git a/tests/test_actinia_vector_management.py b/tests/test_actinia_vector_management.py new file mode 100644 index 0000000..f8ac1f3 --- /dev/null +++ b/tests/test_actinia_vector_management.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +####### +# actinia-python-client is a python client for actinia - an open source REST +# API for scalable, distributed, high performance processing of geographical +# data that uses GRASS GIS for computational tasks. +# +# Copyright (c) 2022 mundialis GmbH & Co. KG +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####### + +__license__ = "GPLv3" +__author__ = "Anika Weinmann" +__copyright__ = "Copyright 2022, mundialis GmbH & Co. KG" +__maintainer__ = "Anika Weinmann" + +import json +import os +from unittest.mock import Mock, patch + +from actinia import Actinia +from actinia.vector import Vector + +from .mock.actinia_mock import ( + ACTINIA_BASEURL, + version_resp_text, + location_get_mapset_resp, +) +from .mock.actinia_location_management_mock import get_locations_resp +from .mock.actinia_vector_management_mock import ( + get_vectors_mock, + get_vector_info_mock, + vector_info_resp, + upload_vector_resp, + delete_vector_resp, + start_job_resp, +) + +LOCATION_NAME = "nc_spm_08" +MAPSET_NAME = "PERMANENT" +VECTOR_NAME = "boundary_county" +UPLOAD_VECTOR_GEOJSON = "./data/firestations.geojson" +UPLOAD_VECTOR_NAME = "test_vector" + + +class TestActiniaVector(object): + @classmethod + def setup_class(cls): + cls.mock_get_patcher = patch("actinia.actinia.requests.get") + cls.mock_post_patcher = patch("actinia.actinia.requests.post") + cls.mock_delete_patcher = patch("actinia.actinia.requests.delete") + cls.mock_get = cls.mock_get_patcher.start() + cls.mock_post = cls.mock_post_patcher.start() + cls.mock_delete = cls.mock_delete_patcher.start() + + cls.mock_get.return_value = Mock() + cls.mock_get.return_value.status_code = 200 + cls.mock_get.return_value.text = json.dumps(version_resp_text) + cls.testactinia = Actinia(ACTINIA_BASEURL) + assert isinstance(cls.testactinia, Actinia) + cls.testactinia.set_authentication("user", "pw") + # get locations + cls.mock_get.return_value = Mock() + cls.mock_get.return_value.status_code = 200 + cls.mock_get.return_value.text = json.dumps(get_locations_resp) + cls.testactinia.get_locations() + # get mapsets + cls.mock_get.return_value = Mock() + cls.mock_get.return_value.status_code = 200 + cls.mock_get.return_value.text = json.dumps(location_get_mapset_resp) + cls.testactinia.locations[LOCATION_NAME].get_mapsets() + + @classmethod + def teardown_class(cls): + cls.mock_get_patcher.stop() + cls.mock_post_patcher.stop() + cls.mock_delete_patcher.stop() + + def test_get_vectors_and_vector_info(self): + """Test get_vector_layers and get_info methods.""" + # get vector layers + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(get_vectors_mock) + resp = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].get_vector_layers() + + assert isinstance(resp, dict), "response is not a dictionary" + assert VECTOR_NAME in resp, f"'{VECTOR_NAME}' vector not in response" + assert isinstance( + resp[VECTOR_NAME], Vector + ), "Vector not of type Vector" + assert resp == self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].vector_layers + + # get vector info + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(get_vector_info_mock) + resp = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].vector_layers[VECTOR_NAME].get_info() + + vector = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].vector_layers[VECTOR_NAME] + assert isinstance(resp, dict), "response is not a dictionary" + assert vector_info_resp == resp, "response is not correct" + assert vector.info == resp, "vector info is not set correctly" + assert vector.region is not None, "vector region is not set" + + def test_upload_and_delete_vector(self): + """Test upload_vector and delete_vector methods.""" + # mockup + self.mock_get.return_value = Mock() + self.mock_get.return_value.status_code = 200 + self.mock_get.return_value.text = json.dumps(upload_vector_resp) + self.mock_post.return_value = Mock() + self.mock_post.return_value.status_code = 200 + self.mock_post.return_value.text = json.dumps(start_job_resp) + self.mock_delete.return_value = Mock() + self.mock_delete.return_value.status_code = 200 + self.mock_delete.return_value.text = json.dumps(delete_vector_resp) + + # upload + dir_path = os.path.dirname(os.path.realpath(__file__)) + tif_path = os.path.join(dir_path, UPLOAD_VECTOR_GEOJSON) + self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].upload_vector(UPLOAD_VECTOR_NAME, tif_path) + vector_layers = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].vector_layers + assert UPLOAD_VECTOR_NAME in vector_layers + + # delete + self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].delete_vector(UPLOAD_VECTOR_NAME) + vector_layers = self.testactinia.locations[LOCATION_NAME].mapsets[ + MAPSET_NAME].vector_layers + assert UPLOAD_VECTOR_NAME not in vector_layers From f1c08e9b2c60056b86798c35cd652fb3a3cf7621 Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 8 Jul 2022 13:40:03 -0400 Subject: [PATCH 18/19] Fixing issue caused during merge --- actinia/mapset.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/actinia/mapset.py b/actinia/mapset.py index 7549324..a41f991 100644 --- a/actinia/mapset.py +++ b/actinia/mapset.py @@ -36,10 +36,6 @@ from actinia.utils import request_and_check, print_stdout from actinia.job import Job -from actinia.raster import Raster -from actinia.vector import Vector -from actinia.utils import request_and_check, print_stdout -from actinia.job import Job @unique class MAPSET_TASK(Enum): From e12a53b3c1600baa093d83099acd9698d0398686 Mon Sep 17 00:00:00 2001 From: Corey White Date: Fri, 8 Jul 2022 13:46:07 -0400 Subject: [PATCH 19/19] Mapsets: Removed vscode settings from git --- .gitignore | 3 +++ .vscode/settings.json | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index ea62f53..905cbf6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ site/ # Per-project virtualenvs env/ + +# vscode settigns +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 491b1a1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.linting.enabled": true, - "python.linting.flake8Enabled": true -} \ No newline at end of file