With ArcGIS GeoEvent it is very easy to setup a notification when a new feature is added to an ArcGIS Online Hosted Feature Service. Instructions can be found here. If you do not have GeoEvent you can still accomplish this using Python. Below is an example on how to do this.
When editor tracking is enabled, new features are recorded in a field called created_date. The script queries this field within the service and compares the time to the current time (minus one hour). If the time is greater than the current time, it will send an e-mail with specified field values for the new feature(s).
Windows Task Scheduler can be setup to execute this script at a given interval (i.e. every hour). This will report any new features within that last hour.
The below example is written for the Citizen Problem Report application. It utilizes 2 fields (reqtype & status) when querying the service. These variables can be commented out, or updated to reference different fields, if working with a different service:
The SUBJECT and TEXT variables in the sendEmail function can be updated to whatever you would like to include in the e-mail:
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
import datetime, time, smtplib
from datetime import timedelta
from email.mime.text import MIMEText
# Variables
username = 'jskinner_rats' # AGOL Username
password = '********' # AGOL Password
fsURL = 'https://services.arcgis.com/dfRzIozUOVYoi65F/arcgis/rest/services/Requests/FeatureServer/0' # Feature Service URL
dateField = 'created_date' # Date field to query
hours = 1 # Number of hours to check when a feature was added
fromEmail = 'no-reply@esri.com' # From e-mail address
toEmail = ['user1@esri.com', 'user2@esri.com'] # To e-mail address(es)
smtpServer = 'smtp.esri.com' # SMTP Server Name
portNumber = 25 # SMTP Server port
# Function to send email
def sendEmail():
SUBJECT = 'Problem Reported'
TEXT = f"{reqtype} was created with status {status}"
smtpObj = smtplib.SMTP(host=smtpServer, port=portNumber)
msg = MIMEText(TEXT)
msg['Subject'] = SUBJECT
msg['From'] = fromEmail
msg['To'] = ", ".join(toEmail)
smtpObj.sendmail(fromEmail, toEmail, msg.as_string())
print("Successfully sent email")
smtpObj.quit()
# Generate AGOL token
print('Connecting to AGOL')
gis = GIS('https://www.arcgis.com', username, password)
# Reference feature service from url
fLyr = FeatureLayer(fsURL)
# Query service and check if date field is within the last hour
print("Querying feature service")
queryResults = fLyr.query(where='1=1', out_fields="*", return_geometry=False)
for results in queryResults.features:
if results.attributes[dateField] != None:
createDate = results.attributes[dateField] / 1000
reqtype = results.attributes['reqtype']
status = results.attributes['status']
t = datetime.datetime.now() - timedelta(hours=hours)
t = time.mktime(t.timetuple())
if createDate > t:
sendEmail()
Yes, you can dynamically use field values. The query results are returning all field values, so you could do something similar to below. The example is assigning an variable status with the value from a field called Status in the feature service, then using this in the e-mail message.
# Query service and check if date field is within the last hour
print("Querying feature service")
queryResults = fLyr.query(where='1=1', out_fields="*", return_geometry=False)
for results in queryResults.features:
if results.attributes[dateField] != None:
status = results.attributes['Status']
emailMessage = f"Status is currently {status}"
createDate = results.attributes[dateField] / 1000
t = datetime.datetime.now() + timedelta(weeks=weeks_check)
t = time.mktime(t.timetuple())
if createDate > t:
# Send e-mail
print('Sending e-mail')
allUsersGroup.notify(users=userList, subject=emailSubject, message=emailMessage)
@JakeSkinner I've altered the original script by adding in the extra 'status' and 'emailMessage' parameter, changing the name of the field to the match the dataset. The issue is it keeps stalling at the "Querying feature service" section, it just won't go any further in the script. Any ideas what it could be please?
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
import datetime, time
from datetime import timedelta
# Variables
fsURL = 'https://services6.arcgis.com/h0X6cdaELg2mgWNZ/arcgis/rest/services/My_Sites_Copy/FeatureServer/0'
dateField = 'Agreement_Date_End'
weeks_check = 26
allUsersGroupID = 'aed962a2e2574c0895644002c512a37a'
emailSubject = 'TEST - URGENT - Agreement Date'
emailMessage = 'This is a test email. Please ignore.'
# Generate AGOL
print('Connecting to AGOL')
gis = GIS("home")
# Get group members
allUsersGroup = gis.groups.get(allUsersGroupID)
userList = allUsersGroup.get_members()
# Reference feature service from url
fLyr = FeatureLayer(fsURL)
# Query service and check if date field is within the next 26 weeks
print("Querying feature service")
queryResults = fLyr.query(where='1=1', out_fields="*", return_geometry=False)
for results in queryResults.features:
if results.attributes[dateField] != None:
status = results.attributes['Site_Status']
emailMessage = f"Status is currently {status}"
createDate = results.attributes[dateField] / 1000
t = datetime.datetime.now() + timedelta(weeks=weeks_check)
t = time.mktime(t.timetuple())
if createDate > t:
# Send e-mail
print('Sending e-mail')
allUsersGroup.notify(users=userList, subject=emailSubject, message=emailMessage)
@Kevin_MacLeod if you have GeoEvent, using that application would be the easiest. It would be too many steps to detail here, but if you contact Tech Support, they should be able to provide you assistance.
@James_Shreeve I only see one date in the feature service, and it's a few weeks in the future:
So you will need to change the below line from > to <, and it will generate an e-mail:
Also, I found an issue with how group members are being returned, may be due to an update, but replace:
# Get group members
allUsersGroup = gis.groups.get(allUsersGroupID)
userList = allUsersGroup.get_members()
with the following:
# Get group members
allUsersGroup = gis.groups.get(allUsersGroupID)
userList = allUsersGroup.get_members()['users']
for admin in allUsersGroup.get_members()['admins']:
userList.append(admin)
and you should be good.
Thank you so much for your help @JakeSkinner, i've made the changes and ran this today and it's worked a treat. I'll now make some tweaks to the attributes it brings through and the content of the email to make it more valuable in terms of content.
This will be massively beneficial for us. Thanks again!
Is it possible to run this over an entire feature service and not just one layer? I have a feature service with 4 different layers on it or better yet I have 3 feature services with 4 different layers on each.
They are utility services that are separated by individual utilities for a city client. It would be nice for it to tell me if my field staff collected anything in each service, a simple email would do like:
A new {utility} feature was in collected {city}.
from arcgis.gis import GIS
from arcgis.features import FeatureLayer
import datetime, time, smtplib
from datetime import timedelta
from email.mime.text import MIMEText
# Variables
username = 'REDACTED'
password = 'REDACTED'
fsURL = 'FEATURE SERVICE'
dateField = 'created_date'
hours = 24
fromEmail = 'REDACTED'
toEmail = ['cREDACTED']
smtpServer = 'smtp.office365.com'
portNumber = 587
office365_user = 'REDACTED'
office365_pwd = 'REDACTED'
# Function to send email
def sendEmail():
SUBJECT = 'New Feature Created - City'
TEXT = f"There was a new feature created for City"
smtpObj = smtplib.SMTP(host=smtpServer, port=portNumber)
smtpObj.starttls()
smtpObj.ehlo()
smtpObj.login(office365_user,office365_pwd)
msg = MIMEText(TEXT)
msg['Subject'] = SUBJECT
msg['From'] = fromEmail
msg['To'] = ", ".join(toEmail)
smtpObj.sendmail(fromEmail, toEmail, msg.as_string())
print("Successfully sent email")
smtpObj.quit()
# Generate AGOL token
print('Connecting to Portal')
gis = GIS('REDACTED', 'REDACTED', 'REDACTED')
# Reference feature service from url
fLyr = FeatureLayer(fsURL)
# Query service and check if date field is within the last 24 hours
print("Querying feature service")
queryResults = fLyr.query(where='1=1', out_fields="*", return_geometry=False)
for results in queryResults.features:
if results.attributes[dateField] != None:
createDate = results.attributes[dateField] / 1000
reqtype = results.attributes['assetgroup']
status = results.attributes['assettype']
t = datetime.datetime.now() - timedelta(hours=hours)
t = time.mktime(t.timetuple())
if createDate > t:
sendEmail()
@JakeSkinner you've helped me previously and am now noticing some behaviour i'm trying to stop, but finding it difficult to be honest.
Below is the snip of code which queries the FS and sends the email, this is currently finding all records which firstly has a valid date field then anything which is less than 6 months in the future from todays date. I'm noticing this is now highlighting everything, even dates before todays date. Ideally I don't want this to bring up sites before today. I've tried adding extra 'if' statements and a few extra things but I just get various issues around floats and strings. I feel this is probably quite a simple fix but i'm stumped. Any help would be greatly recieved, thanks!
# Query service and check if date field is within the next 26 weeks
print("Querying feature service")
queryResults = fLyr.query(where='1=1', out_fields="*", return_geometry=False)
for results in queryResults.features:
if results.attributes[dateField] != None:
print ('Site with valid agreement end date found')
ESCO_Ref = results.attributes['ESCO_Ref']
emailMessage = f"The agreement date for site {ESCO_Ref} is running out within the next 6 months, please action. "
createDate = results.attributes[dateField] / 1000
t = datetime.datetime.now() + timedelta(weeks=weeks_check)
t2 = time.mktime(t.timetuple())
if createDate < t2:
print ('Site with agreement date before 6 months in the future')
# Send e-mail
print('Sending e-mail')
allUsersGroup.notify(users=userList, subject=emailSubject, message=emailMessage)
else:
print ('Not within 6 months')
@James_Shreeve I should have done this from the start, but we can make the initial query the features you want to return. Take a look at this document on querying feature services. Once you have the syntax figured out to query your features, you can replace .query(where='1=1') in use this instead. You can test the query is working using the REST endpoint of the service.
@JakeSkinner thanks for linking me to the document, found it very useful and managed to use it to create the syntax for finding the features I wanted.
Agreement_Date_End BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL 183 DAY
I have then replaced the .query(where='1=1') as seen below.
# Query service and check if date field is within the next 26 weeks
print("Querying feature service")
queryResults = fLyr.query(where="Agreement_Date_End BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '183' DAY", out_fields="*", return_geometry=False)
for results in queryResults.features:
if results.attributes[dateField] != None:
print ('Site with valid agreement end date found')
ESCO_Ref = results.attributes['ESCO_Ref']
emailMessage = f"The agreement date for site {ESCO_Ref} is running out within the next 6 months, please action. "
createDate = results.attributes[dateField] / 1000
t = datetime.datetime.now() + timedelta(weeks=weeks_check)
t2 = time.mktime(t.timetuple())
if createDate < t2:
print ('Site with agreement date before 6 months in the future')
# Send e-mail
print('Sending e-mail')
allUsersGroup.notify(users=userList, subject=emailSubject, message=emailMessage)
else:
print ('Not within 6 months')
I've tested this a far bit now with different dates etc. and now excludes the dates previous to the run date of the script, which is great! Thanks again for your help with this.