|
| 1 | +import arcpy |
| 2 | +from openlocationcode import openlocationcode as olc |
| 3 | + |
| 4 | +# https://github.com/google/open-location-code/blob/master/python/openlocationcode_test.py |
| 5 | + |
| 6 | + |
| 7 | +def check_spatial_reference(feature_class): |
| 8 | + sr = arcpy.Describe(feature_class).spatialReference |
| 9 | + if sr.factoryCode != 4326: |
| 10 | + raise ValueError('The input feature class must be in WHS84 (WKID 4326)') |
| 11 | + |
| 12 | + |
| 13 | +def validate_plus_code_length(code): |
| 14 | + |
| 15 | + # Those are the number of digit required from the specifications. |
| 16 | + valid_codes_length = [ |
| 17 | + 2, # Level 0 |
| 18 | + 4, # Level 1 |
| 19 | + 6, # Level 2 |
| 20 | + 8, # Level 3 |
| 21 | + 10, # Level 4 |
| 22 | + 11, # Level 5 |
| 23 | + 12 # Level 6 |
| 24 | + ] |
| 25 | + |
| 26 | + if code not in valid_codes_length: |
| 27 | + raise ValueError('Valid Plus Code must be one of the following value: {}'.format( |
| 28 | + ', '.join([str(code) for code in valid_codes_length]) |
| 29 | + )) |
| 30 | + |
| 31 | + |
| 32 | +def validate_code_field_length(feature_class, plus_code_field, code_length): |
| 33 | + fields = [field for field in arcpy.ListFields(feature_class) if field.name.lower() == plus_code_field.lower()] |
| 34 | + if fields is None or len(fields) == 0: |
| 35 | + raise Exception('The field {} does not exist'.format(plus_code_field)) |
| 36 | + |
| 37 | + field = fields[0] |
| 38 | + if field.type != 'String': |
| 39 | + raise Exception('The field for plus code must be of type String') |
| 40 | + if field.length < (code_length + 1): |
| 41 | + raise Exception('The field {} is not long enough. Field Length: {} - Plus Code Length: {} - Plus Code Length Required: {}'.format( |
| 42 | + field.name, |
| 43 | + field.length, |
| 44 | + code_length, |
| 45 | + code_length + 1 |
| 46 | + )) |
| 47 | + |
| 48 | + |
| 49 | +def generate_plus_code(feature_class, plus_code_field, code_length): |
| 50 | + """ |
| 51 | + Generate the plus code based on a input feature class. The centroid of the geometry is used. |
| 52 | +
|
| 53 | + :param feature_class: The input feature class. The EPSG must be 4326 |
| 54 | + :param plus_code_field: The field that will contain the plus codes. |
| 55 | + :param code_length: The maximum length for the plus code. |
| 56 | + :return: |
| 57 | + """ |
| 58 | + |
| 59 | + # Make sure that the input feature class is in the appropriate format. |
| 60 | + check_spatial_reference(feature_class) |
| 61 | + |
| 62 | + # Validate that the plus code is a valid value |
| 63 | + validate_plus_code_length(code_length) |
| 64 | + |
| 65 | + # Validate the field is long enough to accomodate the code. |
| 66 | + validate_code_field_length(feature_class, plus_code_field, code_length) |
| 67 | + |
| 68 | + count = 0 |
| 69 | + with arcpy.da.UpdateCursor(feature_class, ['SHAPE@XY', plus_code_field, 'OID@']) as input_cursor: |
| 70 | + for input_row in input_cursor: |
| 71 | + |
| 72 | + oid = input_row[-1] |
| 73 | + count += 1 |
| 74 | + |
| 75 | + if count % 100000 == 0: |
| 76 | + arcpy.AddMessage('Processed {} rows so far ...'.format(count)) |
| 77 | + |
| 78 | + try: |
| 79 | + longitude = input_row[0][0] |
| 80 | + latitude = input_row[0][1] |
| 81 | + plus_code = olc.encode(latitude, longitude, code_length) |
| 82 | + input_row[1] = plus_code |
| 83 | + input_cursor.updateRow(input_row) |
| 84 | + except Exception as ex: |
| 85 | + arcpy.AddWarning( |
| 86 | + 'Something went wrong with feature OID: {} - Error Message: {}'.format(oid, ex.message) |
| 87 | + ) |
| 88 | + |
| 89 | + |
| 90 | +if __name__ == '__main__': |
| 91 | + input_feature_class = arcpy.GetParameterAsText(0) |
| 92 | + output_plus_code_field = arcpy.GetParameterAsText(1) |
| 93 | + output_code_length = int(arcpy.GetParameterAsText(2)) |
| 94 | + try: |
| 95 | + generate_plus_code(input_feature_class, output_plus_code_field, output_code_length) |
| 96 | + except Exception as ex: |
| 97 | + arcpy.AddError(ex) |
| 98 | + |
0 commit comments