|
POST
|
Thanks for the additional info, James. So we're on the same page, can you provide a snippet I test on my end to see if I encounter the same behavior? Kind regards, Earl
... View more
06-13-2019
06:55 AM
|
0
|
0
|
785
|
|
POST
|
Hi Greta, From the little bit you shared the code looks correct. I tested on version 1.6.1 and I've got this working with Feature Collections or individual layers. Can you tell us more about the data types (point, polyline, polygon)? From visual inspection do you expect this to return a result? The error seems to indicate that the response is invalid (or it is valid but it's not being decoded properly). If you add the below lines to the script we can get a better sense of what's going out and what's coming in: import logging logging.basicConfig(level=logging.DEBUG) -Earl
... View more
06-12-2019
03:09 PM
|
0
|
2
|
4892
|
|
POST
|
Hi James, You added the Feature Layer then as an item with the url "http://myhost/testing/MyData/FeatureServer/0" ? Can you tell us a little bit more about how you are adding the layer? Are you adding that from your content (drag and drop) or via the script?
... View more
06-12-2019
07:38 AM
|
0
|
2
|
785
|
|
POST
|
Hi there, I assume you want SELinux to remain enabled and probably don't want it in permissive mode. If so, then I think the other other option would be to make an exception for the process by flagging it with the unconfined option. I'm not 100% positive on what the proper place to configure that would be. Possibly you would just throw this line into the systemd service itself: SELinuxContext=system_u:system_r:unconfined_t:s0
... View more
05-21-2019
09:29 PM
|
0
|
0
|
1492
|
|
POST
|
If the documentation lists that then I believe it may need to be corrected. As Asujit noted, I have recently seen this error occur with other databases including MSSQL and PostgreSQL. William, in my case (MSSQL 2017) I was able to resolve my connection issue by creating a new user with the db_datareader role.
... View more
05-16-2019
03:45 PM
|
0
|
2
|
1623
|
|
BLOG
|
These days, it is quite common for people to use the rasterio, rasterstats, numpy, or geopandas Python packages in their Raster processing/analysis workflows. This post aims to illustrate how some of these packages might be used to perform zonal statistics: rasterio rasterstats geopandas fiona If you're ever used the Summarize Raster Within or Zonal Statistics tools and you've wondered a bit about what goes on behind the scenes his article will help break down a few of the processes. Prerequisites: A number of packages are required for the below script to work - some of these packages are dependent on gdal. The easiest way to get started is to use Anaconda to create a new environment: conda create -n raster python=3.7 conda install -n raster gdal From there, you can use conda/pip to install the remainder of the packages: rasterio rasterstats descartes mapclassify geopandas fiona shapely matplotlib Overview: When you run a tool like Zonal Statistics or Summarize Raster Within, you are asked for a few things: Input Zone Layer Zone Field Input Raster Layer to Summarize Statistic Type As an example, let's say you're working with this then: A polygon feature class of parcels Owner Name field DEM Average In the equivalent workflow presented here, the following occurs: The polygon feature class is loaded into data frame > dissolved by the zone field > > statistics are calculated using the zones > the result is rasterized. Begin by importing the required packages: import fiona, rasterio
import geopandas as gpd
from rasterio.plot import show
import matplotlib.pyplot as plt
import rasterio.plot as rplt
from rasterio.features import rasterize
from rasterstats import zonal_stats In order to run the required tools, it helps to view the data - the below help with adding a bit of interactivity: def enum_items(source):
print("\n")
for ele in enumerate(source):
print(ele)
def list_columns(df):
field_list = list(df)
enum_items(field_list)
return field_list These functions are used to load data into a geopandas dataframe for easy manipulation. Either an .shp or a File Geodatabase Feature class (the input vector layer). # For loading feature classes into geopandas dataframe
def loadfc_as_gpd(fgdb):
layers = fiona.listlayers(fgdb)
enum_items(layers)
index = int(input("Which index to load? "))
fcgpd = gpd.read_file(fgdb,layer=layers[index])
return fcgpd
# For loading shapefiles into geopandas dataframe
def loadshp_as_gpd(shp):
data = gpd.read_file(shp)
return data If you want to filter the data before processing (e.g., maybe you only want to use parcels within a certain county): # For optional filtering of data
def filter_gpd(fcgpd):
field_list = list_columns(fcgpd)
index = int(input("Filter by which field (index)? "))
field = field_list[index]
value = input("Enter value: ")
result = fcgpd[fcgpd[field] == value]
return result Ideally, the input vector layer is in the same projection as the input raster layer. If it's not, this function can be used to re-project the vector layer to the raster's projection. The re-projected vector layer is plotted along with the raster data for visual inspection: # For re-projecting input vector layer to raster projection
def reproject(fcgpd, raster):
proj = raster.crs.to_proj4()
print("Original vector layer projection: ", fcgpd.crs)
reproj = fcgpd.to_crs(proj)
print("New vector layer projection (PROJ4): ", reproj.crs)
fig, ax = plt.subplots(figsize=(15, 15))
rplt.show(raster, ax=ax)
reproj.plot(ax=ax, facecolor='none', edgecolor='red')
fig.show()
return reproj Note: the to_proj4 method is introduced in later versions of rasterio (e.g. 1.0.22). This is used to perform a dissolve on the selected zone field: # For dissolving geopandas dataframe by selected field
def dissolve_gpd(df):
field_list = list_columns(df)
index = int(input("Dissolve by which field (index)? "))
dgpd = df.dissolve(by=field_list[index])
return dgpd Here desired statistics are selected - I've included a list of common statistics but rasterstats offers additional options. You can actually select multiple statistics to calculate and this may be one reason why someone would choose to incorporate a tool like rasterstats in their workflow (Zonal Statistics as Table will let you get multiple statistics, but not Zonal Statistics/Summarize Raster Within😞 # For selecting which raster statistics to calculate
def stats_select():
stats_list = ['min', 'max', 'mean', 'count',
'sum', 'std', 'median', 'majority',
'minority', 'unique', 'range']
enum_items(stats_list)
indices = input("Enter raster statistics selections separated by space: ")
stats = list(indices.split())
out_stats = list()
for i in stats:
out_stats.append(stats_list[int(i)])
return out_stats
# For calculating zonal statistics
def get_zonal_stats(vector, raster, stats):
# Run zonal statistics, store result in geopandas dataframe
result = zonal_stats(vector, raster, stats=stats, geojson_out=True)
geostats = gpd.GeoDataFrame.from_features(result)
return geostats The result of rasterstats' zonal_stats function is not a raster (e.g. .tif) by default so you must rasterize the result. In the previous function, the result is stored as GeoJSON in a geopandas dataframe and this is what gets rasterized to a .tif. # For generating raster from zonal statistics result
def stats_to_raster(zdf, raster, stats, out_raster, no_data='y'):
meta = raster.meta.copy()
out_shape = raster.shape
transform = raster.transform
dtype = raster.dtypes[0]
field_list = list_columns(stats)
index = int(input("Rasterize by which field? "))
zone = zdf[field_list[index]]
shapes = ((geom,value) for geom, value in zip(zdf.geometry, zone))
burned = rasterize(shapes=shapes, fill=0, out_shape=out_shape, transform=transform)
show(burned)
meta.update(dtype=rasterio.float32, nodata=0)
# Optional to set nodata values to min of stat
if no_data == 'y':
cutoff = min(zone.values)
print("Setting nodata cutoff to: ", cutoff)
burned[burned < cutoff] = 0
with rasterio.open(out_raster, 'w', **meta) as out:
out.write_band(1, burned)
print("Zonal Statistics Raster generated") Altogether the script is: import fiona, rasterio
import geopandas as gpd
from rasterio.plot import show
import matplotlib.pyplot as plt
import rasterio.plot as rplt
from rasterio.features import rasterize
from rasterstats import zonal_stats
def enum_items(source):
print("\n")
for ele in enumerate(source):
print(ele)
def list_columns(df):
field_list = list(df)
enum_items(field_list)
return field_list
# For loading feature classes into geopandas dataframe
def loadfc_as_gpd(fgdb):
layers = fiona.listlayers(fgdb)
enum_items(layers)
index = int(input("Which index to load? "))
fcgpd = gpd.read_file(fgdb,layer=layers[index])
return fcgpd
# For loading shapefiles into geopandas dataframe
def loadshp_as_gpd(shp):
data = gpd.read_file(shp)
return data
# For optional filtering of data
def filter_gpd(fcgpd):
field_list = list_columns(fcgpd)
index = int(input("Filter by which field (index)? "))
field = field_list[index]
value = input("Enter value: ")
result = fcgpd[fcgpd[field] == value]
return result
# For re-projecting input vector layer to raster projection (Rasterio v. 1.0.22)
def reproject(fcgpd, raster):
proj = raster.crs.to_proj4()
print("Original vector layer projection: ", fcgpd.crs)
reproj = fcgpd.to_crs(proj)
print("New vector layer projection (PROJ4): ", reproj.crs)
fig, ax = plt.subplots(figsize=(15, 15))
rplt.show(raster, ax=ax)
reproj.plot(ax=ax, facecolor='none', edgecolor='red')
fig.show()
return reproj
# For dissolving geopandas dataframe by selected field
def dissolve_gpd(df):
field_list = list_columns(df)
index = int(input("Dissolve by which field (index)? "))
dgpd = df.dissolve(by=field_list[index])
return dgpd
# For selecting which raster statistics to calculate
def stats_select():
stats_list = ['min', 'max', 'mean', 'count',
'sum', 'std', 'median', 'majority',
'minority', 'unique', 'range']
enum_items(stats_list)
indices = input("Enter raster statistics selections separated by space: ")
stats = list(indices.split())
out_stats = list()
for i in stats:
out_stats.append(stats_list[int(i)])
return out_stats
# For calculating zonal statistics
def get_zonal_stats(vector, raster, stats):
# Run zonal statistics, store result in geopandas dataframe
result = zonal_stats(vector, raster, stats=stats, geojson_out=True)
geostats = gpd.GeoDataFrame.from_features(result)
return geostats
# For generating raster from zonal statistics result
def stats_to_raster(zdf, raster, stats, out_raster, no_data='y'):
meta = raster.meta.copy()
out_shape = raster.shape
transform = raster.transform
dtype = raster.dtypes[0]
field_list = list_columns(stats)
index = int(input("Rasterize by which field? "))
zone = zdf[field_list[index]]
shapes = ((geom,value) for geom, value in zip(zdf.geometry, zone))
burned = rasterize(shapes=shapes, fill=0, out_shape=out_shape, transform=transform)
show(burned)
meta.update(dtype=rasterio.float32, nodata=0)
# Optional to set nodata values to min of stat
if no_data == 'y':
cutoff = min(zone.values)
print("Setting nodata cutoff to: ", cutoff)
burned[burned < cutoff] = 0
with rasterio.open(out_raster, 'w', **meta) as out:
out.write_band(1, burned)
print("Zonal Statistics Raster generated")
def main():
rst = '/path/to.raster.tif'
raster = rasterio.open(rst)
fgdb = '/path/to/fgdb.gdb'
vector = loadfc_as_gpd(fgdb)
p_vector = reproject(vector, raster)
d_vector = dissolve_gpd(p_vector)
out_rst = '/path/to/out.tif'
stats_to_get = stats_select()
zs = get_zonal_stats(d_vector, rst, stats_to_get)
stats_to_raster(zs, raster, stats_to_get, out_rst)
if __name__ == '__main__':
main() Usage: As an example, suppose we have the following data where the vector layer is a feature class of of urban areas and the raster layer is a DEM: Start by setting the paths in the main function of the final script then run either in a terminal or a notebook (I ran it in a notebook). The first prompt will ask you which Feature Class in the File Geodatabase you would like to load. Enter the index: From review of the data, I know the vector layer needs to be projected so I included the reproject function in main. Here you can see that the data was re-projected to North America Albers Equal Area Conic (the proj4 representation of this projection is shown): The result is plotted for visual inspection: Next, a prompt to choose which field to dissolve on: Here I select the statistics I want to calculate, separating the selections by space: Finally, the statistic to use for rasterization is selected: The result is plotted: This compares well with the result you would get from the Zonal Statistics tool:
... View more
05-07-2019
07:19 AM
|
6
|
0
|
21583
|
|
POST
|
For the benefit of others who find this page: In ArcGIS Pro, this option is called "Allow assignment of unique numeric IDs for sharing web layers": https://pro.arcgis.com/en/pro-app/help/sharing/overview/introduction-to-sharing-web-layers.htm#ESRI_SECTION1_11B19A48477F49FDBD61038AE0B851B2
... View more
04-30-2019
12:24 PM
|
4
|
0
|
2097
|
|
POST
|
Hi Dave, I just thought I'd mention that it is possible to generate a CSV report for inactive users. In that scenario you could set a small threshold like 1 day or a few hours, perhaps. That's not quite what you're looking for but it's the next best thing. -Earl
... View more
04-26-2019
09:09 AM
|
0
|
0
|
1525
|
|
POST
|
Hi Renaud, This boils down to how the item is defined. For a good idea, see the second note in this post: https://community.esri.com/groups/arcgis-python-api/blog/2019/04/09/updating-layer-symbology-with-the-arcgis-api-for-python. My advice would be to create a Web Map as you want it to appear and then look at the definition. -Earl
... View more
04-15-2019
07:38 PM
|
1
|
2
|
3446
|
|
POST
|
Hi Mitchell, I think what's going on here is that you need to "generate" a definition for the Enterprise item. There's the Feature Service Layer Definition, which you can find here for example: https://sampleserver6.arcgisonline.com/arcgis/rest/services/Earthquakes_Since1970/FeatureServer/0?f=pjson But there's also a definition for the ArcGIS Online/Portal for ArcGIS Item - you can easily generate a definition for the item by making a small change to the layer and saving the layer (e.g., change the symbology). -Earl
... View more
04-12-2019
07:44 AM
|
0
|
0
|
2329
|
|
POST
|
Hi Ben, For info on what Distance Units should be refer to this page: Distance—ArcGIS REST API: Services Directory | ArcGIS for Developers which branches off into: esriSRUnitType Constants esriSRUnit2Type Constants It seems the function fails when you supply the distance unit directly (this might constitute a defect). However, it works when you store it in a variable like so: from arcgis import geometry
sr = 102100
distance_unit = 9001
geom1 = {'x': -7703308.762905646, 'y': -2937284.4374484867,
'spatialReference': {'wkid': 102100}}
geom2 = {'x': 13614373.72405075, 'y': 2571663.047185693,
'spatialReference': {'wkid': 102100}}
pt1 = geometry.Point(geom1)
pt2 = geometry.Point(geom2)
d = arcgis.geometry.distance(sr, pt1, pt2, distance_unit, False)
print(d) The result is: {'distance': 22017994.663527936} Kind regards, Earl
... View more
04-09-2019
01:59 PM
|
1
|
0
|
4269
|
|
POST
|
Hi all, This is still something that is under consideration. If you'd like to raise awareness, I invite you to contact Esri Support Services to get added to the enhancement log: ENH-000088243: Allow for services published to an ArcGIS Online organization to be consumed in ArcGIS for AutoCAD. Kind regards, Earl
... View more
04-09-2019
11:30 AM
|
2
|
1
|
3225
|
|
BLOG
|
Hi Richard, I'm pretty sure what you're asking about is described in Egge-Jan Pollé 's post here: https://community.esri.com/people/EPolle_TensingInternational/blog/2019/03/27/create-view-from-hosted-feature-layer-and-define-area-of-interest For a more general explanation of how to accomplish this see this post: https://community.esri.com/thread/230204-arcgis-api-for-python-set-an-area-of-interest-on-a-hosted-feature-layer-view Kind regards, Earl
... View more
04-09-2019
10:24 AM
|
1
|
0
|
9413
|
|
BLOG
|
Quite often, people wonder how to use the ArcGIS API for Python to update layer symbology. After a quick search through posts and documentation, one may discover that there are a number of ways to accomplish this and find oneself uncertain of how to proceed. If you land on this page my hope is that everything will start to make a bit more sense. In this post I'll go over: How to update layer symbology in a Web Map How to update layer symbology on a layer item How to update symbology on a Feature Service Layer If the above all seem the same don't worry - presently, the distinctions between #1, #2, and #3 will become clear and you will get a better sense of when you might want to choose one method over another. Note: Although I'm illustrating how to update symbology, you can apply the same concepts to update/configure other properties such as pop-ups, labels, etc. I encourage you to build upon the below standalone scripts or use only the parts you need! How to Update Layer Symbology in a Web Map: Let's suppose you add this Map Service to your content in ArcGIS Online/Portal for ArcGIS: https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer You add the item to a Web Map and for a little while it suits your needs: But one day you decide that you don't really like the color orange and would get more use out of the Web Map if you could better visualize total population of each county. After some time, you settle on the symbology included in the attached webmaplyr.json. It's a bit a long so I won't include it here - this is fine because you probably won't want to define the JSON in your script anyway, preferring to read from a file. Note: If you don't know where to get started as far as generating your own JSON don't fret. Simply start by creating a Web Map exactly as you want it to appear (apply styles, labels, options, etc.). From there, you can go to (where itemID is the item id of the Web Map): Portal: https://machine.domain.com/portal/sharing/rest/content/items/<itemID>/data ArcGIS Online: https://www.arcgis.com/sharing/content/items/<itemID>/data Share (temporarily) the item to 'Everyone' if you don't want to have to supply a token. Alternatively, you can get the JSON using the ArcGIS API for Python with a few lines: from arcgis import GIS
import json
conn = GIS("https://machine.domain.com/portal", "admin", "password")
item = conn.content.get(<itemID>)
item_data = item.get_data()
# Include the below line for prettified JSON
print(json.dumps(item_data, indent=4, sort_keys=True))
The below script illustrates how to read your prepared JSON from a file and apply it to the Web Map: from arcgis import GIS
import json, sys
def search_item(conn,layer_name):
search_results = conn.content.search(layer_name, item_type='Web Map')
proper_index = [i for i, s in enumerate(search_results) if
'"'+layer_name+'"' in str(s)]
found_item = search_results[proper_index[0]]
get_item = conn.content.get(found_item.id)
return get_item
def update_wm_layerdef(item):
item_data = item.get_data()
print("*******************ORIGINAL DEFINITION*********************")
print(json.dumps(item_data, indent=4, sort_keys=True))
# Open JSON file containing symbology update
with open('/path/to/webmaplyr.json') as json_data:
data = json.load(json_data)
# Set the item_properties to include the desired update
item_properties = {"text": json.dumps(data)}
# 'Commit' the updates to the Item
item.update(item_properties=item_properties)
# Print item_data to see that changes are reflected
new_item_data = item.get_data()
print("***********************NEW DEFINITION**********************")
print(json.dumps(new_item_data, indent=4, sort_keys=True))
def main():
conn = GIS("https://machine.domain.com/portal",
"admin", "password")
# Search for item, get item data)
item = search_item(conn, 'wm_lyrsym')
update_wm_layerdef(item)
if __name__ == '__main__':
sys.exit(main())
After the script runs, you end up with the below: How to Update Portal/ArcGIS Online Item Symbology: The difference here is subtle. In Option #1, the item being updated is a Web Map. Here, the item being updated is a Feature/Map Image Service Layer. The service might be something you published to ArcGIS Online/Portal for ArcGIS, or a Map/Feature Service you added as an item to your content. Option #1 is great if all you need to do is change the styles in a Web Map, but perhaps you need to change the style for an item in your Organization. Since this item is used by many people (and deliverables are occasionally provided to stakeholders outside of the organization) you wish to standardize its appearance. In this example, I use the same Map Service as before (https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer) and perform a similar update to the symbology, only on the Layer item itself. Note that this example is a bit more specific on how the symbology is updated: layer_def = item_data['layers'][3]['layerDefinition'] Here you see that only the layer at index 3 is updated (counties) - everything else in the service is left alone. Review drawingInfo.json and you can see that the renderer is updated from Single symbol to Classified. You might wonder why this is included: else: print("There is no Layer Definition at the moment..creating one...") create_layer_def(item) A layer definition isn't necessarily created by default. Let's suppose I only just added that Map Service as an item to my content - that ArcGIS Online/Portal for ArcGIS item has no layer definition attached it. If added the item to a Web Map, made a simple symbology change, and saved the layer this would generate an layer definition. complete.json contains the layer definitions that would apply to the entire item (all 4 layers). from arcgis import GIS
import json, sys
def search_layer(conn,layer_name):
search_results = conn.content.search(layer_name, item_type='*')
proper_index = [i for i, s in enumerate(search_results) if
'"'+layer_name+'"' in str(s)]
found_item = search_results[proper_index[0]]
get_item = conn.content.get(found_item.id)
return get_item
def update_layer_def(item):
item_data = item.get_data()
if item_data is not None:
# Here note we are changing a specific part of the Layer Definition
layer_def = item_data['layers'][3]['layerDefinition']
print("*******************ORIGINAL DEFINITION*********************")
print(json.dumps(item_data, indent=4, sort_keys=True))
# Open JSON file containing symbology update
with open('/path/to/drawingInfo.json') as json_data:
data = json.load(json_data)
# Set the drawingInfo equal to what is in JSON file
layer_def['drawingInfo'] = data
# Set the item_properties to include the desired update
item_properties = {"text": json.dumps(item_data)}
# 'Commit' the updates to the Item
item.update(item_properties=item_properties)
# Print item_data to see that changes are reflected
new_item_data = item.get_data()
print("***********************NEW DEFINITION**********************")
print(json.dumps(new_item_data, indent=4, sort_keys=True))
else:
print("There is no Layer Definition at the moment..creating one...")
create_layer_def(item)
def create_layer_def(item):
with open('/path/to/complete.json') as json_data:
data = json.load(json_data)
# Set the item_properties to include the desired update
item_properties = {"text": json.dumps(data)}
# 'Commit' the updates to the Item
item.update(item_properties=item_properties)
# Print item_data to see that changes are reflected
item_data = item.get_data()
print("*********************CREATED DEFINITION************************")
print(json.dumps(item_data, indent=4, sort_keys=True))
def main():
conn = GIS("https://machine.domain.com/portal",
"admin", "password")
# Search for item, get item data)
item = search_layer(conn, 'earl_api_usalyraw')
# Attempt to update Layer Definition
update_layer_def(item)
if __name__ == '__main__':
sys.exit(main()) How to Update Feature Service Symbology: In this last example, I illustrate how to update symbology on a Feature Service Layer. Behind the scenes, this method really just performs this operation: Update Definition (Feature Layer)—ArcGIS REST API: Services Directory | ArcGIS for Developers So, what's the difference here and when would you want to use this approach? This will only work for Feature Services. You would want to use this approach when you want to make high-level updates to your Feature Service Layers. This can be used to standardize the appearance of Feature Service Layers across the board - without making any client-side modifications, someone adding the Feature Service to a ArcGIS Online/Portal for ArcGIS Web Map would see the same thing as a developer consuming the service in an application. For this example, I published a Hosted Feature Service containing 2 layers: US States/Canada Provinces North America Major Railroads The original Feature Service looks like this: The JSON for this example isn't very long. I just make a few changes to color and width on the States/Provinces layer: {
"drawingInfo": {
"renderer": {
"type": "simple",
"symbol": {
"type": "esriSFS",
"style": "esriSFSSolid",
"color": [
202,
46,
204,
105
],
"outline": {
"type": "esriSLS",
"style": "esriSLSSolid",
"color": [
10,
10,
210,
55
],
"width": 0.5
}
}
},
"scaleSymbols": true,
"transparency": 0,
"labelingInfo": null
}
} In this scenario, since we're updating a Feature Service and not an item we need to use arcgis.features. from arcgis import GIS
from arcgis.features import FeatureLayerCollection
import json, sys
def search_layer(conn,layer_name):
search_results = conn.content.search(layer_name, item_type='Feature Layer')
proper_index = [i for i, s in enumerate(search_results) if
'"'+layer_name+'"' in str(s)]
found_item = search_results[proper_index[0]]
flc = FeatureLayerCollection.fromitem(found_item)
return flc
def update_layer_def(layer):
# Open JSON file containing symbology update
with open('/path/to/hosted_drawinfo_lyr.json') as json_data:
data = json.load(json_data)
layer.manager.update_definition(data)
print("*******************UPDATED DEFINITION**********************")
print(layer.properties)
def main():
conn = GIS("https://machine.domain.com/portal",
"admin", "password")
# Search for item, get item data)
flc = search_layer(conn, 'layerdef')
layer = flc.layers[1]
print(layer.properties)
update_layer_def(layer)
if __name__ == '__main__':
sys.exit(main())
The States/Provinces layer was at index 1. The update result: That concludes this overview on how to update layer symbology. Hopefully with this guide you can get a good sense of how to implement similar workflows in your organization!
... View more
04-09-2019
09:25 AM
|
17
|
19
|
22752
|
|
BLOG
|
Hi Richard, I'm afraid I don't quite understand your question. Can you explain further?
... View more
04-08-2019
04:37 PM
|
0
|
0
|
9413
|
| Title | Kudos | Posted |
|---|---|---|
| 2 | 01-18-2024 01:34 PM | |
| 1 | 09-13-2023 06:48 AM | |
| 1 | 09-23-2022 09:04 AM | |
| 1 | 06-14-2024 01:14 PM | |
| 2 | 09-24-2019 08:22 AM |