From a9c4c101f12074268620555cac6d5b421012a240 Mon Sep 17 00:00:00 2001 From: Ian Dees Date: Sun, 17 Nov 2024 09:23:36 -0600 Subject: [PATCH 1/2] Remove case smashing --- openaddr/cache.py | 9 +- openaddr/conform.py | 113 ++++-------------- openaddr/tests/__init__.py | 18 +-- openaddr/tests/conform.py | 25 +--- .../tests/sources/us-ca-san_francisco.json | 4 +- openaddr/tests/sources/us/or/portland.json | 4 +- 6 files changed, 40 insertions(+), 133 deletions(-) diff --git a/openaddr/cache.py b/openaddr/cache.py index f06f0ef3..7148fb59 100644 --- a/openaddr/cache.py +++ b/openaddr/cache.py @@ -340,7 +340,7 @@ def field_names_to_request(cls, source_config): fields = set() for k, v in conform.items(): - if k.upper() in source_config.SCHEMA: + if k in source_config.SCHEMA: if isinstance(v, dict): # It's a function of some sort? if 'function' in v: @@ -384,8 +384,6 @@ def download(self, source_urls, workdir, source_config): if GEOM_FIELDNAME not in field_names: field_names.append(GEOM_FIELDNAME) - field_names = list(map(lambda x: x.upper(), field_names)) - # Get the count of rows in the layer try: row_count = downloader.get_feature_count() @@ -410,11 +408,6 @@ def download(self, source_urls, workdir, source_config): shp = shape(feature['geometry']) row[GEOM_FIELDNAME] = shp.wkt - r = dict() - for k,v in row.items(): - r[k.upper()] = v - row = r - writer.writerow({fn: row.get(fn) for fn in field_names}) size += 1 except TypeError: diff --git a/openaddr/conform.py b/openaddr/conform.py index 2904aaae..e33ef85d 100644 --- a/openaddr/conform.py +++ b/openaddr/conform.py @@ -42,14 +42,14 @@ def gdal_error_handler(err_class, err_num, err_msg): # Field names for use in cached CSV files. # We add columns to the extracted CSV with our own data with these names. -GEOM_FIELDNAME = 'OA:GEOM' +GEOM_FIELDNAME = 'oa:geom' -ADDRESSES_SCHEMA = [ 'HASH', 'NUMBER', 'STREET', 'UNIT', 'CITY', 'DISTRICT', 'REGION', 'POSTCODE', 'ID' ] -BUILDINGS_SCHEMA = [ 'HASH'] -PARCELS_SCHEMA = [ 'HASH', 'PID' ] +ADDRESSES_SCHEMA = [ 'hash', 'number', 'street', 'unit', 'city', 'district', 'region', 'postcode', 'id' ] +BUILDINGS_SCHEMA = [ 'hash'] +PARCELS_SCHEMA = [ 'hash', 'pid' ] RESERVED_SCHEMA = ADDRESSES_SCHEMA + BUILDINGS_SCHEMA + PARCELS_SCHEMA + [ - "LAT", - "LON" + "lat", + "lon" ] UNZIPPED_DIRNAME = 'unzipped' @@ -612,7 +612,6 @@ def csv_source_to_csv(source_config, source_path, dest_path): source_config.data_source["conform"]["lat"] ] - old_ll.extend([s.upper() for s in old_ll]) out_fieldnames = [fn for fn in reader.fieldnames if fn not in old_ll] out_fieldnames.append(GEOM_FIELDNAME) @@ -722,19 +721,12 @@ def row_extract_and_reproject(source_config, source_row): lat_name = data_source["conform"]["lat"] lon_name = data_source["conform"]["lon"] - if lon_name in source_row: - source_x = source_row[lon_name] - else: - source_x = source_row[lon_name.upper()] - - if lat_name in source_row: - source_y = source_row[lat_name] - else: - source_y = source_row[lat_name.upper()] + source_x = source_row[lon_name] + source_y = source_row[lat_name] # Remove lat/lng name from output row - for n in lon_name, lon_name.upper(), lat_name, lat_name.upper(): - if n in out_row: del out_row[n] + for n in (lon_name, lat_name): + out_row.pop(n, None) # Convert commas to periods for decimal numbers. (Not using locale.) try: @@ -751,7 +743,6 @@ def row_extract_and_reproject(source_config, source_row): out_row[GEOM_FIELDNAME] = None return out_row - # Reproject the coordinates if necessary if "srs" in data_source["conform"] and data_source["conform"]["srs"] != "EPSG:4326": try: @@ -818,17 +809,14 @@ def row_function(sc, row, key, fxn): def row_transform_and_convert(source_config, row): "Apply the full conform transform and extract operations to a row" - # Some conform specs have fields named with a case different from the source - row = row_smash_case(source_config.data_source, row) - c = source_config.data_source["conform"] "Attribute tags can utilize processing fxns" for k, v in c.items(): - if k.upper() in source_config.SCHEMA and type(v) is list: + if k in source_config.SCHEMA and isinstance(v, list): "Lists are a concat shortcut to concat fields with spaces" row = row_merge(source_config, row, k) - if k.upper() in source_config.SCHEMA and type(v) is dict: + if k in source_config.SCHEMA and isinstance(v, dict): "Dicts are custom processing functions" row = row_function(source_config, row, k, v) @@ -847,49 +835,6 @@ def row_transform_and_convert(source_config, row): return feat -def fxn_smash_case(fxn): - if "field" in fxn: - fxn["field"] = fxn["field"].lower() - if "fields" in fxn: - fxn["fields"] = [s.lower() for s in fxn["fields"]] - if "field_to_remove" in fxn: - fxn["field_to_remove"] = fxn["field_to_remove"].lower() - if "functions" in fxn: - for sub_fxn in fxn["functions"]: - fxn_smash_case(sub_fxn) - -def conform_smash_case(data_source): - "Convert all named fields in data_source object to lowercase. Returns new object." - new_sd = copy.deepcopy(data_source) - conform = new_sd["conform"] - - for k, v in conform.items(): - if type(conform[k]) is str and k.upper() in RESERVED_SCHEMA: - conform[k] = v.lower() - if type(conform[k]) is list: - conform[k] = [s.lower() for s in conform[k]] - if type(conform[k]) is dict: - fxn_smash_case(conform[k]) - - if "functions" in conform[k] and type(conform[k]["functions"]) is list: - for function in conform[k]["functions"]: - if type(function) is dict: - if "field" in function: - function["field"] = function["field"].lower() - - if "fields" in function: - function["fields"] = [s.lower() for s in function["fields"]] - - if "field_to_remove" in function: - function["field_to_remove"] = function["field_to_remove"].lower() - - return new_sd - -def row_smash_case(sc, input): - "Convert all field names to lowercase. Slow, but necessary for imprecise conform specs." - output = { k.lower() : v for (k, v) in input.items() } - return output - def row_merge(sc, row, key): "Merge multiple columns like 'Maple','St' to 'Maple St'" merge_data = [row[field] for field in sc.data_source["conform"][key]] @@ -1016,7 +961,7 @@ def row_fxn_chain(sc, row, key, fxn): original_key = key - if var and var.upper().lstrip('OA:') not in sc.SCHEMA and var not in row: + if var and var.lstrip('oa:') not in sc.SCHEMA and var not in row: row['oa:' + var] = u'' key = var @@ -1026,7 +971,7 @@ def row_fxn_chain(sc, row, key, fxn): if row.get('oa:' + key): row[key] = row['oa:' + key] - row['oa:{}'.format(original_key.lower())] = row['oa:{}'.format(key)] + row['oa:{}'.format(original_key)] = row['oa:{}'.format(key)] return row @@ -1082,7 +1027,7 @@ def row_calculate_hash(cache_fingerprint, row): def row_convert_to_out(source_config, row): "Convert a row from the source schema to OpenAddresses output schema" - geom = row.get(GEOM_FIELDNAME.lower(), None) + geom = row.get(GEOM_FIELDNAME, None) if geom == "POINT EMPTY" or geom == '': geom = None @@ -1096,20 +1041,19 @@ def row_convert_to_out(source_config, row): wkt_parsed = wkt_loads(output["geometry"]) output["geometry"] = mapping(wkt_parsed) - for field in source_config.SCHEMA: - if row.get('oa:{}'.format(field.lower())) is not None: + if row.get('oa:{}'.format(field)) is not None: # If there is an OA prefix, it is not a native field and was compiled - # via an attrib funciton or concatentation - output["properties"][field.lower()] = row.get('oa:{}'.format(field.lower())) + # via an attrib function or concatenation + output["properties"][field] = row.get('oa:{}'.format(field)) else: # Get a native field as specified in the conform object - cfield = source_config.data_source['conform'].get(field.lower()) + cfield = source_config.data_source['conform'].get(field) if cfield: - output["properties"][field.lower()] = row.get(cfield.lower()) + output["properties"][field] = row.get(cfield) else: - output["properties"][field.lower()] = '' + output["properties"][field] = '' return output @@ -1150,9 +1094,6 @@ def transform_to_out_geojson(source_config, extract_path, dest_path): extract_path: extracted CSV file to process dest_path: path for output file in OpenAddress CSV ''' - # Convert all field names in the conform spec to lower case - source_config.data_source = conform_smash_case(source_config.data_source) - # Read through the extract CSV with open(extract_path, 'r', encoding='utf-8') as extract_fp: reader = csv.DictReader(extract_fp) @@ -1192,13 +1133,6 @@ def conform_cli(source_config, source_path, dest_path): def check_source_tests(source_config): ''' Return boolean status and a message if any tests failed. ''' - try: - # Convert all field names in the conform spec to lower case - source_config.data_source = conform_smash_case(source_config.data_source) - except: - # There may be problems in the source spec - ignore them for now. - source_config.data_source = source_config.data_source - source_test = source_config.data_source.get('test', {}) tests_enabled = source_test.get('enabled', True) acceptance_tests = source_test.get('acceptance-tests') @@ -1208,11 +1142,10 @@ def check_source_tests(source_config): return None, None for (index, test) in enumerate(acceptance_tests): - input = row_smash_case(source_config.data_source, test['inputs']) - output = row_smash_case(source_config.data_source, row_transform_and_convert(source_config, input)) + output = row_transform_and_convert(source_config, test['inputs']) actual = {k: v for (k, v) in output['properties'].items() if k in test['expected']} - expected = row_smash_case(source_config.data_source, test['expected']) + expected = test['expected'] if actual != expected: expected_json = json.dumps(expected, ensure_ascii=False) diff --git a/openaddr/tests/__init__.py b/openaddr/tests/__init__.py index be71dbca..79f05a33 100644 --- a/openaddr/tests/__init__.py +++ b/openaddr/tests/__init__.py @@ -528,7 +528,7 @@ def test_single_car(self): state = dict(zip(*json.load(file))) self.assertIsNotNone(state['cache']) - self.assertEqual(state['fingerprint'], '23082fe4819682a6934b61443560160c') + self.assertEqual(state['fingerprint'], '4a8047f90dbfe176c2a2b148837dae36') self.assertIsNotNone(state['processed']) self.assertIsNotNone(state['preview']) self.assertIsNotNone(state['pmtiles']) @@ -557,7 +557,7 @@ def test_single_car_cached(self): state = dict(zip(*json.load(file))) self.assertIsNotNone(state['cache']) - self.assertEqual(state['fingerprint'], '1821b2e50a61ed04ac2213fbc7a1984d') + self.assertEqual(state['fingerprint'], '056bdaab3334e709bf29b0b2f1fcf8c4') self.assertIsNotNone(state['processed']) self.assertIsNone(state['preview']) @@ -576,7 +576,7 @@ def test_single_car_old_cached(self): state = dict(zip(*json.load(file))) self.assertIsNotNone(state['cache']) - self.assertEqual(state['fingerprint'], '1821b2e50a61ed04ac2213fbc7a1984d') + self.assertEqual(state['fingerprint'], '056bdaab3334e709bf29b0b2f1fcf8c4') self.assertIsNotNone(state['processed']) self.assertIsNone(state['preview']) @@ -1019,7 +1019,7 @@ def test_single_tx_waco(self): self.assertEqual(rows[0]['properties']['region'], u'TX') self.assertEqual(rows[0]['properties']['id'], u'') self.assertEqual(rows[0]['properties']['number'], u'308') - self.assertEqual(rows[0]['properties']['hash'], u'431f816eebac0000') + self.assertEqual(rows[0]['properties']['hash'], u'5b2957c31a02e00e') self.assertEqual(rows[0]['properties']['city'], u'Mcgregor') self.assertEqual(rows[0]['geometry']['coordinates'], [-97.3961768, 31.4432706]), self.assertEqual(rows[0]['properties']['street'], u'PULLEN ST') @@ -1046,7 +1046,7 @@ def test_single_wy_park(self): rows = list(map(json.loads, list(input))) self.assertEqual(rows[0]['properties']['id'], u'') self.assertEqual(rows[0]['properties']['number'], u'162') - self.assertEqual(rows[0]['properties']['hash'], u'730e5ad1893108e4') + self.assertEqual(rows[0]['properties']['hash'], u'0488f0771f0ff30f') self.assertEqual(rows[0]['properties']['city'], u'') self.assertEqual(rows[0]['geometry']['type'], 'Point'); self.assertAlmostEqual(rows[0]['geometry']['coordinates'][0], -108.7563613); @@ -1075,7 +1075,7 @@ def test_single_ny_orange(self): rows = list(map(json.loads, list(input))) self.assertEqual(rows[0]['properties']['id'], u'') self.assertEqual(rows[0]['properties']['number'], u'434') - self.assertEqual(rows[0]['properties']['hash'], u'8cb84b9e793a4986') + self.assertEqual(rows[0]['properties']['hash'], u'd129b77ffa481fea') self.assertEqual(rows[0]['properties']['city'], u'MONROE') self.assertEqual(rows[0]['geometry']['coordinates'], [-74.1926686, 41.3187728]) self.assertEqual(rows[0]['properties']['street'], u'') @@ -1246,9 +1246,9 @@ def test_single_us_nj_statewide(self): self.assertEqual(rows[1]['properties']['street'], u'Sagamore Avenue') self.assertEqual(rows[2]['properties']['number'], u'47') self.assertEqual(rows[2]['properties']['street'], u'Seneca Place') - self.assertEqual(rows[0]['geometry']['coordinates'], [-74.0012016, 40.3201199]), - self.assertEqual(rows[1]['geometry']['coordinates'], [-74.0027904, 40.3203365]) - self.assertEqual(rows[2]['geometry']['coordinates'], [-74.0011386, 40.3166497]) + self.assertEqual(rows[0]['geometry']['coordinates'], [-74.0012025, 40.3201201]), + self.assertEqual(rows[1]['geometry']['coordinates'], [-74.0027913, 40.3203367]) + self.assertEqual(rows[2]['geometry']['coordinates'], [-74.0011395, 40.3166499]) def test_single_cz_countrywide(self): ''' Test complete process_one.process on data. diff --git a/openaddr/tests/conform.py b/openaddr/tests/conform.py index fa0dbb7a..ad7185b3 100644 --- a/openaddr/tests/conform.py +++ b/openaddr/tests/conform.py @@ -17,13 +17,13 @@ from ..conform import ( GEOM_FIELDNAME, csv_source_to_csv, find_source_path, row_transform_and_convert, - row_fxn_regexp, row_smash_case, row_merge, + row_fxn_regexp, row_merge, row_extract_and_reproject, row_convert_to_out, row_fxn_join, row_fxn_format, row_fxn_prefixed_number, row_fxn_postfixed_street, row_fxn_postfixed_unit, row_fxn_remove_prefix, row_fxn_remove_postfix, row_fxn_chain, row_fxn_first_non_empty, row_fxn_constant, - row_canonicalize_unit_and_number, conform_smash_case, conform_cli, + row_canonicalize_unit_and_number, conform_cli, convert_regexp_replace, normalize_ogr_filename_case, is_in, geojson_source_to_csv, check_source_tests ) @@ -36,24 +36,6 @@ def wkt_pt(pt_str): class TestConformTransforms (unittest.TestCase): "Test low level data transform functions" - def test_row_smash_case(self): - r = row_smash_case(None, {"UPPER": "foo", "lower": "bar", "miXeD": "mixed"}) - self.assertEqual({"upper": "foo", "lower": "bar", "mixed": "mixed"}, r) - - def test_conform_smash_case(self): - d = { "conform": { "street": [ "U", "l", "MiXeD" ], "number": "U", "lat": "Y", "lon": "x", - "city": { "function": "join", "fields": ["ThIs","FiELd"], "separator": "-" }, - "district": { "function": "regexp", "field": "ThaT", "pattern": ""}, - "postcode": { "function": "join", "fields": ["MiXeD", "UPPER"], "separator": "-" } } } - r = conform_smash_case(d) - - self.maxDiff = None - self.assertEqual({ "conform": { "street": [ "u", "l", "mixed" ], "number": "u", "lat": "y", "lon": "x", - "city": {"fields": ["this", "field"], "function": "join", "separator": "-"}, - "district": { "field": "that", "function": "regexp", "pattern": ""}, - "postcode": { "function": "join", "fields": ["mixed", "upper"], "separator": "-" } } }, - r) - def test_row_convert_to_out(self): d = SourceConfig(dict({ "schema": 2, @@ -68,7 +50,7 @@ def test_row_convert_to_out(self): r = row_convert_to_out(d, { "s": "MAPLE LN", "n": "123", - GEOM_FIELDNAME.lower(): "POINT (-119.2 39.3)" + GEOM_FIELDNAME: "POINT (-119.2 39.3)" }) self.assertEqual({ @@ -2362,4 +2344,3 @@ def test_no_tests(self): result, message = check_source_tests(source) self.assertIsNone(result, 'Tests should not exist in {}'.format(filename)) self.assertIsNone(message, 'No message expected from {}'.format(filename)) - diff --git a/openaddr/tests/sources/us-ca-san_francisco.json b/openaddr/tests/sources/us-ca-san_francisco.json index a77d08cb..8e6080ee 100644 --- a/openaddr/tests/sources/us-ca-san_francisco.json +++ b/openaddr/tests/sources/us-ca-san_francisco.json @@ -18,10 +18,10 @@ "lat": "y", "number": { "function": "regexp", - "field": "address", + "field": "ADDRESS", "pattern": "^(\\S+)" }, - "street": ["st_name", "st_type"], + "street": ["ST_NAME", "ST_TYPE"], "format": "shapefile" } }] diff --git a/openaddr/tests/sources/us/or/portland.json b/openaddr/tests/sources/us/or/portland.json index 9b02e848..44ea49e9 100644 --- a/openaddr/tests/sources/us/or/portland.json +++ b/openaddr/tests/sources/us/or/portland.json @@ -7,8 +7,8 @@ "compression": "zip", "conform": { "number": "address_number", - "lon": "x", - "lat": "y", + "lon": "X", + "lat": "Y", "format": "csv", "street": [ "str_predir_code", From c22480372c35b05b9507b915051e95be9260767f Mon Sep 17 00:00:00 2001 From: Ian Dees Date: Sun, 17 Nov 2024 11:03:43 -0600 Subject: [PATCH 2/2] Fix tests --- openaddr/tests/__init__.py | 6 +++--- openaddr/tests/conform.py | 12 +++++++----- openaddr/tests/conforms/lake-man-split2.csv | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/openaddr/tests/__init__.py b/openaddr/tests/__init__.py index 79f05a33..18a20408 100644 --- a/openaddr/tests/__init__.py +++ b/openaddr/tests/__init__.py @@ -1246,9 +1246,9 @@ def test_single_us_nj_statewide(self): self.assertEqual(rows[1]['properties']['street'], u'Sagamore Avenue') self.assertEqual(rows[2]['properties']['number'], u'47') self.assertEqual(rows[2]['properties']['street'], u'Seneca Place') - self.assertEqual(rows[0]['geometry']['coordinates'], [-74.0012025, 40.3201201]), - self.assertEqual(rows[1]['geometry']['coordinates'], [-74.0027913, 40.3203367]) - self.assertEqual(rows[2]['geometry']['coordinates'], [-74.0011395, 40.3166499]) + self.assertEqual(rows[0]['geometry']['coordinates'], [-74.0012016, 40.3201199]), + self.assertEqual(rows[1]['geometry']['coordinates'], [-74.0027904, 40.3203365]) + self.assertEqual(rows[2]['geometry']['coordinates'], [-74.0011386, 40.3166497]) def test_single_cz_countrywide(self): ''' Test complete process_one.process on data. diff --git a/openaddr/tests/conform.py b/openaddr/tests/conform.py index ad7185b3..d7a15c8f 100644 --- a/openaddr/tests/conform.py +++ b/openaddr/tests/conform.py @@ -518,7 +518,7 @@ def test_row_extract_and_reproject(self): }] } }), "addresses", "default") - r = row_extract_and_reproject(d, {"OA:GEOM": "POINT (-122.3 39.1)" }) + r = row_extract_and_reproject(d, {"oa:geom": "POINT (-122.3 39.1)"}) self.assertEqual({GEOM_FIELDNAME: "POINT (-122.3 39.1)"}, r) # reprojection @@ -2248,7 +2248,9 @@ def test_perverse_header_name_and_case(self): # This is an example inspired by the hipsters in us-or-portland # Conform says lowercase but the actual header is uppercase. # Also the columns are named X and Y in the input - c = {"conform": {"lon": "x", "lat": "y", "number": "n", "street": "s", "format": "csv"}, 'protocol': 'test'} + # 2024-11-17 (idees): Previously, this was testing that column names + # were case-insensitive, but removed in https://github.com/openaddresses/batch-machine/pull/65 + c = {"conform": {"lon": "X", "lat": "Y", "number": "n", "street": "s", "format": "csv"}, 'protocol': 'test'} d = (u'n,s,X,Y'.encode('ascii'), u'3203,SE WOODSTOCK BLVD,-122.629314,45.479425'.encode('ascii')) r = self._convert(c, d) @@ -2257,7 +2259,7 @@ def test_perverse_header_name_and_case(self): def test_srs(self): # This is an example inspired by the hipsters in us-or-portland - c = {"conform": {"lon": "x", "lat": "y", "srs": "EPSG:2913", "number": "n", "street": "s", "format": "csv"}, 'protocol': 'test'} + c = {"conform": {"lon": "X", "lat": "Y", "srs": "EPSG:2913", "number": "n", "street": "s", "format": "csv"}, 'protocol': 'test'} d = (u'n,s,X,Y'.encode('ascii'), u'3203,SE WOODSTOCK BLVD,7655634.924,668868.414'.encode('ascii')) r = self._convert(c, d) @@ -2287,7 +2289,7 @@ def test_esri_csv(self): c = { "protocol": "ESRI", "conform": { "format": "geojson", "lat": "theseare", "lon": "ignored" } } d = ( - u'STREETNAME,NUMBER,OA:GEOM'.encode('ascii'), + u'STREETNAME,NUMBER,oa:geom'.encode('ascii'), u'MAPLE ST,123,POINT (-121.2 39.3)'.encode('ascii') ) @@ -2299,7 +2301,7 @@ def test_esri_csv_no_lat_lon(self): # Test that the ESRI path works even without lat/lon tags. See issue #91 c = { "protocol": "ESRI", "conform": { "format": "geojson" } } d = ( - u'STREETNAME,NUMBER,OA:GEOM'.encode('ascii'), + u'STREETNAME,NUMBER,oa:geom'.encode('ascii'), u'MAPLE ST,123,POINT (-121.2 39.3)'.encode('ascii') ) r = self._convert(c, d) diff --git a/openaddr/tests/conforms/lake-man-split2.csv b/openaddr/tests/conforms/lake-man-split2.csv index 102120b1..3ac4f580 100644 --- a/openaddr/tests/conforms/lake-man-split2.csv +++ b/openaddr/tests/conforms/lake-man-split2.csv @@ -1,4 +1,4 @@ -OWNER1,ApLot,OWNER2,SITECITYST,SITESTREET,PREFIX,STTYPE,OWNERLAST,MAILZIP4,id,OBJECTID,APTUNIT,PHONE,APNUMBER,ApPage,MAILZIP,ZIP4,ZIP,STNAME,OWNERFIRST,HNUM,LEGAL,MAILCITYST,ApBook,CARRIER,MAILNAME,CITY,MAILSTREET,NUNITS,OA:x,OA:y,OA:geom +OWNER1,ApLot,OWNER2,SITECITYST,SITESTREET,PREFIX,STTYPE,OWNERLAST,MAILZIP4,id,OBJECTID,APTUNIT,PHONE,APNUMBER,ApPage,MAILZIP,ZIP4,ZIP,STNAME,OWNERFIRST,HNUM,LEGAL,MAILCITYST,ApBook,CARRIER,MAILNAME,CITY,MAILSTREET,NUNITS,OA:x,OA:y,oa:geom Serrano Heights Community,34,Assn, , ,,,,2288,left arm,2807,,,085-531-34,531,92630,,,,,,T 4 R 9 Sec 11 Por S1/2 And T 4 R 9 Sec 14 Por Ne1/4 (also Being Por. Par 1pm. 264-29.),Lake Forest Ca,085,,Serrano Heights Community,,1 Spectrum Pointe Dr #320,,-122.259249687,37.8026126376,POINT (-122.259250 37.802613 0) ,36,, , ,,,,,right arm,2899,,,085-531-36,531,,,,,,,,,085,,,,,,-122.256717682,37.8025278661,POINT (-122.256718 37.802528 0) City Of Orange,32,, , ,,,,1508,torso,3419,,,085-531-32,531,92866,,,,,,T 4 R 9 Sec 11 Por S1/2,Orange Ca,085,,City Of Orange,,300 E Chapman Ave,,-122.257940769,37.8029686768,POINT (-122.257941 37.802969 0)