[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-\u002Fblog\u002Ftownship-america-python-sdk-plss-lookups-in-python-for-gis-scripts-and-spatial-data-pipelines":3},{"id":4,"title":5,"body":6,"cover":1259,"date":1260,"description":1261,"draft":1262,"extension":1263,"meta":1264,"navigation":102,"path":1267,"seo":1268,"stem":1269,"tags":1270,"__hash__":1275},"blog\u002Fblog\u002Ftownship-america-python-sdk-plss-lookups-in-python-for-gis-scripts-and-spatial-data-pipelines.md","Township America Python SDK: PLSS Lookups in Python for GIS Scripts and Spatial Data Pipelines",{"type":7,"value":8,"toc":1252},"minimark",[9,13,18,21,24,28,31,57,66,263,276,280,291,790,793,919,922,1089,1093,1096,1189,1197,1201,1204,1227,1240,1248],[10,11,12],"p",{},"A GIS or spatial data pipeline almost never starts with coordinates. It starts with a column of legal descriptions: a parcel table, a lease schedule, or a permit export where every tract reads something like NWSE 12 152N 96W, 5th Principal Meridian. That text is precise and legally correct, and it is also opaque to GeoPandas, GDAL, ArcPy, or PostGIS until something turns it into latitude and longitude. Resolving those descriptions one row at a time, by pasting each into a browser tool, is fine for a handful and impossible for a table that refreshes on a schedule. This guide shows how to do the same conversion in Python with the PLSS Python SDK: install it, authenticate, run a single lookup, batch convert a CSV, and hand the coordinates and parcel boundaries to the GIS tools you already use.",[14,15,17],"h2",{"id":16},"what-the-python-sdk-covers","What the Python SDK covers",[10,19,20],{},"The Township America REST API exposes four endpoints: single lookup, batch, autocomplete, and map tiles. The Python SDK wraps that API in a typed client so you work with Python objects instead of raw HTTP requests and JSON parsing. It resolves PLSS legal descriptions (state, principal meridian, township, range, section, and aliquot part) to coordinates across 30+ PLSS states and 37 principal meridians, down to the 1\u002F256 aliquot part, which is about 2.5 acres. The conversion is calculated from official BLM survey data.",[10,22,23],{},"Two capabilities matter most for GIS work. First, every section and quarter-section lookup returns a full GeoJSON polygon of the actual BLM survey boundary, not just a centroid point, so you get real parcel geometry rather than an estimate. Second, the batch endpoint accepts up to 100 descriptions per request, which is what makes a scheduled pipeline practical.",[14,25,27],{"id":26},"install-and-authenticate","Install and authenticate",[10,29,30],{},"The SDK is published on PyPI, so a standard install pulls it into any virtual environment:",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","pip install townshipamerica\n","bash","",[39,40,41],"code",{"__ignoreMap":37},[42,43,46,50,54],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"sbgvK","pip",[42,51,53],{"class":52},"s_sjI"," install",[42,55,56],{"class":52}," townshipamerica\n",[10,58,59,60,65],{},"Authentication uses an API key. Create one from the developer portal linked on the ",[61,62,64],"a",{"href":63},"\u002Fapi","API page",", then keep it out of source control by reading it from an environment variable. Here is a first lookup in a plain Python script:",[32,67,71],{"className":68,"code":69,"language":70,"meta":37,"style":37},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","import os\nfrom townshipamerica import TownshipAmerica\n\nclient = TownshipAmerica(api_key=os.environ[\"TOWNSHIP_AMERICA_API_KEY\"])\n\nresult = client.search(\"T4N R5E Sec 12 NE Indian Meridian\")\ncentroid = result.centroid\nif centroid is not None:\n    print(centroid.geometry.latitude, centroid.geometry.longitude)\n","python",[39,72,73,83,97,104,153,158,186,202,224],{"__ignoreMap":37},[42,74,75,79],{"class":44,"line":45},[42,76,78],{"class":77},"sVHd0","import",[42,80,82],{"class":81},"su5hD"," os\n",[42,84,86,89,92,94],{"class":44,"line":85},2,[42,87,88],{"class":77},"from",[42,90,91],{"class":81}," townshipamerica ",[42,93,78],{"class":77},[42,95,96],{"class":81}," TownshipAmerica\n",[42,98,100],{"class":44,"line":99},3,[42,101,103],{"emptyLinePlaceholder":102},true,"\n",[42,105,107,110,114,118,122,126,128,131,134,138,141,145,148,150],{"class":44,"line":106},4,[42,108,109],{"class":81},"client ",[42,111,113],{"class":112},"smGrS","=",[42,115,117],{"class":116},"slqww"," TownshipAmerica",[42,119,121],{"class":120},"sP7_E","(",[42,123,125],{"class":124},"s99_P","api_key",[42,127,113],{"class":112},[42,129,130],{"class":116},"os",[42,132,133],{"class":120},".",[42,135,137],{"class":136},"skxfh","environ",[42,139,140],{"class":120},"[",[42,142,144],{"class":143},"sjJ54","\"",[42,146,147],{"class":52},"TOWNSHIP_AMERICA_API_KEY",[42,149,144],{"class":143},[42,151,152],{"class":120},"])\n",[42,154,156],{"class":44,"line":155},5,[42,157,103],{"emptyLinePlaceholder":102},[42,159,161,164,166,169,171,174,176,178,181,183],{"class":44,"line":160},6,[42,162,163],{"class":81},"result ",[42,165,113],{"class":112},[42,167,168],{"class":81}," client",[42,170,133],{"class":120},[42,172,173],{"class":116},"search",[42,175,121],{"class":120},[42,177,144],{"class":143},[42,179,180],{"class":52},"T4N R5E Sec 12 NE Indian Meridian",[42,182,144],{"class":143},[42,184,185],{"class":120},")\n",[42,187,189,192,194,197,199],{"class":44,"line":188},7,[42,190,191],{"class":81},"centroid ",[42,193,113],{"class":112},[42,195,196],{"class":81}," result",[42,198,133],{"class":120},[42,200,201],{"class":136},"centroid\n",[42,203,205,208,211,214,217,221],{"class":44,"line":204},8,[42,206,207],{"class":77},"if",[42,209,210],{"class":81}," centroid ",[42,212,213],{"class":112},"is",[42,215,216],{"class":112}," not",[42,218,220],{"class":219},"s39Yj"," None",[42,222,223],{"class":120},":\n",[42,225,227,231,233,236,238,241,243,246,249,252,254,256,258,261],{"class":44,"line":226},9,[42,228,230],{"class":229},"sptTA","    print",[42,232,121],{"class":120},[42,234,235],{"class":116},"centroid",[42,237,133],{"class":120},[42,239,240],{"class":136},"geometry",[42,242,133],{"class":120},[42,244,245],{"class":136},"latitude",[42,247,248],{"class":120},",",[42,250,251],{"class":116}," centroid",[42,253,133],{"class":120},[42,255,240],{"class":136},[42,257,133],{"class":120},[42,259,260],{"class":136},"longitude",[42,262,185],{"class":120},[10,264,265,266,268,269,271,272,275],{},"The ",[39,267,173],{}," call returns a typed feature collection. The ",[39,270,235],{}," property gives you the representative point, and ",[39,273,274],{},"centroid.geometry"," carries the latitude and longitude. If the key is missing or a description cannot be resolved, the client raises a typed exception mapped to the HTTP status, so a pipeline can catch and log the failing row instead of stopping.",[14,277,279],{"id":278},"batch-convert-a-csv-of-legal-descriptions","Batch convert a CSV of legal descriptions",[10,281,282,283,286,287,290],{},"Most pipelines begin with a file rather than a single string. Assume a CSV named ",[39,284,285],{},"parcels.csv"," with a ",[39,288,289],{},"legal_description"," column. The pattern is: read the column, send it to the batch endpoint in chunks of 100, and collect coordinates and polygon geometry for each row. Splitting into chunks keeps every request inside the batch limit.",[32,292,294],{"className":68,"code":293,"language":70,"meta":37,"style":37},"import os\nimport csv\nimport json\nfrom townshipamerica import TownshipAmerica\n\nclient = TownshipAmerica(api_key=os.environ[\"TOWNSHIP_AMERICA_API_KEY\"])\n\ndef chunked(items, size=100):\n    for start in range(0, len(items), size):\n        yield items[start:start + size]\n\nwith open(\"parcels.csv\", newline=\"\") as f:\n    descriptions = [row[\"legal_description\"] for row in csv.DictReader(f)]\n\nrows = []\nfeatures = []\nfor chunk in chunked(descriptions):\n    for description, fc in zip(chunk, client.batch_search(chunk)):\n        centroid = fc.centroid\n        if centroid is None:\n            print(\"No match:\", description)\n            continue\n        rows.append({\n            \"legal_description\": description,\n            \"latitude\": centroid.geometry.latitude,\n            \"longitude\": centroid.geometry.longitude,\n        })\n        features.append(fc)\n",[39,295,296,302,309,316,326,330,360,364,394,429,457,462,500,549,554,565,575,595,634,649,663,684,690,704,721,744,767,773],{"__ignoreMap":37},[42,297,298,300],{"class":44,"line":45},[42,299,78],{"class":77},[42,301,82],{"class":81},[42,303,304,306],{"class":44,"line":85},[42,305,78],{"class":77},[42,307,308],{"class":81}," csv\n",[42,310,311,313],{"class":44,"line":99},[42,312,78],{"class":77},[42,314,315],{"class":81}," json\n",[42,317,318,320,322,324],{"class":44,"line":106},[42,319,88],{"class":77},[42,321,91],{"class":81},[42,323,78],{"class":77},[42,325,96],{"class":81},[42,327,328],{"class":44,"line":155},[42,329,103],{"emptyLinePlaceholder":102},[42,331,332,334,336,338,340,342,344,346,348,350,352,354,356,358],{"class":44,"line":160},[42,333,109],{"class":81},[42,335,113],{"class":112},[42,337,117],{"class":116},[42,339,121],{"class":120},[42,341,125],{"class":124},[42,343,113],{"class":112},[42,345,130],{"class":116},[42,347,133],{"class":120},[42,349,137],{"class":136},[42,351,140],{"class":120},[42,353,144],{"class":143},[42,355,147],{"class":52},[42,357,144],{"class":143},[42,359,152],{"class":120},[42,361,362],{"class":44,"line":188},[42,363,103],{"emptyLinePlaceholder":102},[42,365,366,370,374,376,380,382,385,387,391],{"class":44,"line":204},[42,367,369],{"class":368},"sbsja","def",[42,371,373],{"class":372},"sGLFI"," chunked",[42,375,121],{"class":120},[42,377,379],{"class":378},"sFwrP","items",[42,381,248],{"class":120},[42,383,384],{"class":378}," size",[42,386,113],{"class":112},[42,388,390],{"class":389},"srdBf","100",[42,392,393],{"class":120},"):\n",[42,395,396,399,402,405,408,410,413,415,418,420,422,425,427],{"class":44,"line":226},[42,397,398],{"class":77},"    for",[42,400,401],{"class":81}," start ",[42,403,404],{"class":77},"in",[42,406,407],{"class":229}," range",[42,409,121],{"class":120},[42,411,412],{"class":389},"0",[42,414,248],{"class":120},[42,416,417],{"class":229}," len",[42,419,121],{"class":120},[42,421,379],{"class":116},[42,423,424],{"class":120},"),",[42,426,384],{"class":116},[42,428,393],{"class":120},[42,430,432,435,438,440,443,446,449,452,454],{"class":44,"line":431},10,[42,433,434],{"class":77},"        yield",[42,436,437],{"class":81}," items",[42,439,140],{"class":120},[42,441,442],{"class":81},"start",[42,444,445],{"class":120},":",[42,447,448],{"class":81},"start ",[42,450,451],{"class":112},"+",[42,453,384],{"class":81},[42,455,456],{"class":120},"]\n",[42,458,460],{"class":44,"line":459},11,[42,461,103],{"emptyLinePlaceholder":102},[42,463,465,468,471,473,475,477,479,481,484,486,489,492,495,498],{"class":44,"line":464},12,[42,466,467],{"class":77},"with",[42,469,470],{"class":229}," open",[42,472,121],{"class":120},[42,474,144],{"class":143},[42,476,285],{"class":52},[42,478,144],{"class":143},[42,480,248],{"class":120},[42,482,483],{"class":124}," newline",[42,485,113],{"class":112},[42,487,488],{"class":143},"\"\"",[42,490,491],{"class":120},")",[42,493,494],{"class":77}," as",[42,496,497],{"class":81}," f",[42,499,223],{"class":120},[42,501,503,506,508,511,514,516,518,520,522,525,528,531,533,536,538,541,543,546],{"class":44,"line":502},13,[42,504,505],{"class":81},"    descriptions ",[42,507,113],{"class":112},[42,509,510],{"class":120}," [",[42,512,513],{"class":81},"row",[42,515,140],{"class":120},[42,517,144],{"class":143},[42,519,289],{"class":52},[42,521,144],{"class":143},[42,523,524],{"class":120},"]",[42,526,527],{"class":77}," for",[42,529,530],{"class":81}," row ",[42,532,404],{"class":77},[42,534,535],{"class":81}," csv",[42,537,133],{"class":120},[42,539,540],{"class":116},"DictReader",[42,542,121],{"class":120},[42,544,545],{"class":116},"f",[42,547,548],{"class":120},")]\n",[42,550,552],{"class":44,"line":551},14,[42,553,103],{"emptyLinePlaceholder":102},[42,555,557,560,562],{"class":44,"line":556},15,[42,558,559],{"class":81},"rows ",[42,561,113],{"class":112},[42,563,564],{"class":120}," []\n",[42,566,568,571,573],{"class":44,"line":567},16,[42,569,570],{"class":81},"features ",[42,572,113],{"class":112},[42,574,564],{"class":120},[42,576,578,581,584,586,588,590,593],{"class":44,"line":577},17,[42,579,580],{"class":77},"for",[42,582,583],{"class":81}," chunk ",[42,585,404],{"class":77},[42,587,373],{"class":116},[42,589,121],{"class":120},[42,591,592],{"class":116},"descriptions",[42,594,393],{"class":120},[42,596,598,600,603,605,608,610,613,615,618,620,622,624,627,629,631],{"class":44,"line":597},18,[42,599,398],{"class":77},[42,601,602],{"class":81}," description",[42,604,248],{"class":120},[42,606,607],{"class":81}," fc ",[42,609,404],{"class":77},[42,611,612],{"class":229}," zip",[42,614,121],{"class":120},[42,616,617],{"class":116},"chunk",[42,619,248],{"class":120},[42,621,168],{"class":116},[42,623,133],{"class":120},[42,625,626],{"class":116},"batch_search",[42,628,121],{"class":120},[42,630,617],{"class":116},[42,632,633],{"class":120},")):\n",[42,635,637,640,642,645,647],{"class":44,"line":636},19,[42,638,639],{"class":81},"        centroid ",[42,641,113],{"class":112},[42,643,644],{"class":81}," fc",[42,646,133],{"class":120},[42,648,201],{"class":136},[42,650,652,655,657,659,661],{"class":44,"line":651},20,[42,653,654],{"class":77},"        if",[42,656,210],{"class":81},[42,658,213],{"class":112},[42,660,220],{"class":219},[42,662,223],{"class":120},[42,664,666,669,671,673,676,678,680,682],{"class":44,"line":665},21,[42,667,668],{"class":229},"            print",[42,670,121],{"class":120},[42,672,144],{"class":143},[42,674,675],{"class":52},"No match:",[42,677,144],{"class":143},[42,679,248],{"class":120},[42,681,602],{"class":116},[42,683,185],{"class":120},[42,685,687],{"class":44,"line":686},22,[42,688,689],{"class":77},"            continue\n",[42,691,693,696,698,701],{"class":44,"line":692},23,[42,694,695],{"class":81},"        rows",[42,697,133],{"class":120},[42,699,700],{"class":116},"append",[42,702,703],{"class":120},"({\n",[42,705,707,710,712,714,716,718],{"class":44,"line":706},24,[42,708,709],{"class":143},"            \"",[42,711,289],{"class":52},[42,713,144],{"class":143},[42,715,445],{"class":120},[42,717,602],{"class":116},[42,719,720],{"class":120},",\n",[42,722,724,726,728,730,732,734,736,738,740,742],{"class":44,"line":723},25,[42,725,709],{"class":143},[42,727,245],{"class":52},[42,729,144],{"class":143},[42,731,445],{"class":120},[42,733,251],{"class":116},[42,735,133],{"class":120},[42,737,240],{"class":136},[42,739,133],{"class":120},[42,741,245],{"class":136},[42,743,720],{"class":120},[42,745,747,749,751,753,755,757,759,761,763,765],{"class":44,"line":746},26,[42,748,709],{"class":143},[42,750,260],{"class":52},[42,752,144],{"class":143},[42,754,445],{"class":120},[42,756,251],{"class":116},[42,758,133],{"class":120},[42,760,240],{"class":136},[42,762,133],{"class":120},[42,764,260],{"class":136},[42,766,720],{"class":120},[42,768,770],{"class":44,"line":769},27,[42,771,772],{"class":120},"        })\n",[42,774,776,779,781,783,785,788],{"class":44,"line":775},28,[42,777,778],{"class":81},"        features",[42,780,133],{"class":120},[42,782,700],{"class":116},[42,784,121],{"class":120},[42,786,787],{"class":116},"fc",[42,789,185],{"class":120},[10,791,792],{},"From here you can write two artifacts. A flat CSV of coordinates is the right output when the next step is a spreadsheet, a database load, or a join back to the source table:",[32,794,796],{"className":68,"code":795,"language":70,"meta":37,"style":37},"with open(\"parcels_coords.csv\", \"w\", newline=\"\") as f:\n    writer = csv.DictWriter(f, fieldnames=[\"legal_description\", \"latitude\", \"longitude\"])\n    writer.writeheader()\n    writer.writerows(rows)\n",[39,797,798,839,890,903],{"__ignoreMap":37},[42,799,800,802,804,806,808,811,813,815,818,821,823,825,827,829,831,833,835,837],{"class":44,"line":45},[42,801,467],{"class":77},[42,803,470],{"class":229},[42,805,121],{"class":120},[42,807,144],{"class":143},[42,809,810],{"class":52},"parcels_coords.csv",[42,812,144],{"class":143},[42,814,248],{"class":120},[42,816,817],{"class":143}," \"",[42,819,820],{"class":52},"w",[42,822,144],{"class":143},[42,824,248],{"class":120},[42,826,483],{"class":124},[42,828,113],{"class":112},[42,830,488],{"class":143},[42,832,491],{"class":120},[42,834,494],{"class":77},[42,836,497],{"class":81},[42,838,223],{"class":120},[42,840,841,844,846,848,850,853,855,857,859,862,864,866,868,870,872,874,876,878,880,882,884,886,888],{"class":44,"line":85},[42,842,843],{"class":81},"    writer ",[42,845,113],{"class":112},[42,847,535],{"class":81},[42,849,133],{"class":120},[42,851,852],{"class":116},"DictWriter",[42,854,121],{"class":120},[42,856,545],{"class":116},[42,858,248],{"class":120},[42,860,861],{"class":124}," fieldnames",[42,863,113],{"class":112},[42,865,140],{"class":120},[42,867,144],{"class":143},[42,869,289],{"class":52},[42,871,144],{"class":143},[42,873,248],{"class":120},[42,875,817],{"class":143},[42,877,245],{"class":52},[42,879,144],{"class":143},[42,881,248],{"class":120},[42,883,817],{"class":143},[42,885,260],{"class":52},[42,887,144],{"class":143},[42,889,152],{"class":120},[42,891,892,895,897,900],{"class":44,"line":99},[42,893,894],{"class":81},"    writer",[42,896,133],{"class":120},[42,898,899],{"class":116},"writeheader",[42,901,902],{"class":120},"()\n",[42,904,905,907,909,912,914,917],{"class":44,"line":106},[42,906,894],{"class":81},[42,908,133],{"class":120},[42,910,911],{"class":116},"writerows",[42,913,121],{"class":120},[42,915,916],{"class":116},"rows",[42,918,185],{"class":120},[10,920,921],{},"A GeoJSON file is the right output when the next step is a map or a spatial join, because it preserves the survey polygon rather than reducing each parcel to a point:",[32,923,925],{"className":68,"code":924,"language":70,"meta":37,"style":37},"collection = {\n    \"type\": \"FeatureCollection\",\n    \"features\": [\n        json.loads(fc.grid.model_dump_json())\n        for fc in features if fc.grid is not None\n    ],\n}\nwith open(\"parcels.geojson\", \"w\") as f:\n    json.dump(collection, f)\n",[39,926,927,937,958,972,999,1027,1032,1037,1068],{"__ignoreMap":37},[42,928,929,932,934],{"class":44,"line":45},[42,930,931],{"class":81},"collection ",[42,933,113],{"class":112},[42,935,936],{"class":120}," {\n",[42,938,939,942,945,947,949,951,954,956],{"class":44,"line":85},[42,940,941],{"class":143},"    \"",[42,943,944],{"class":52},"type",[42,946,144],{"class":143},[42,948,445],{"class":120},[42,950,817],{"class":143},[42,952,953],{"class":52},"FeatureCollection",[42,955,144],{"class":143},[42,957,720],{"class":120},[42,959,960,962,965,967,969],{"class":44,"line":99},[42,961,941],{"class":143},[42,963,964],{"class":52},"features",[42,966,144],{"class":143},[42,968,445],{"class":120},[42,970,971],{"class":120}," [\n",[42,973,974,977,979,982,984,986,988,991,993,996],{"class":44,"line":106},[42,975,976],{"class":81},"        json",[42,978,133],{"class":120},[42,980,981],{"class":116},"loads",[42,983,121],{"class":120},[42,985,787],{"class":116},[42,987,133],{"class":120},[42,989,990],{"class":136},"grid",[42,992,133],{"class":120},[42,994,995],{"class":116},"model_dump_json",[42,997,998],{"class":120},"())\n",[42,1000,1001,1004,1006,1008,1011,1013,1015,1017,1019,1022,1024],{"class":44,"line":155},[42,1002,1003],{"class":77},"        for",[42,1005,607],{"class":81},[42,1007,404],{"class":77},[42,1009,1010],{"class":81}," features ",[42,1012,207],{"class":77},[42,1014,644],{"class":81},[42,1016,133],{"class":120},[42,1018,990],{"class":136},[42,1020,1021],{"class":112}," is",[42,1023,216],{"class":112},[42,1025,1026],{"class":219}," None\n",[42,1028,1029],{"class":44,"line":160},[42,1030,1031],{"class":120},"    ],\n",[42,1033,1034],{"class":44,"line":188},[42,1035,1036],{"class":120},"}\n",[42,1038,1039,1041,1043,1045,1047,1050,1052,1054,1056,1058,1060,1062,1064,1066],{"class":44,"line":204},[42,1040,467],{"class":77},[42,1042,470],{"class":229},[42,1044,121],{"class":120},[42,1046,144],{"class":143},[42,1048,1049],{"class":52},"parcels.geojson",[42,1051,144],{"class":143},[42,1053,248],{"class":120},[42,1055,817],{"class":143},[42,1057,820],{"class":52},[42,1059,144],{"class":143},[42,1061,491],{"class":120},[42,1063,494],{"class":77},[42,1065,497],{"class":81},[42,1067,223],{"class":120},[42,1069,1070,1073,1075,1078,1080,1083,1085,1087],{"class":44,"line":226},[42,1071,1072],{"class":81},"    json",[42,1074,133],{"class":120},[42,1076,1077],{"class":116},"dump",[42,1079,121],{"class":120},[42,1081,1082],{"class":116},"collection",[42,1084,248],{"class":120},[42,1086,497],{"class":116},[42,1088,185],{"class":120},[14,1090,1092],{"id":1091},"load-the-results-into-geopandas-and-qgis","Load the results into GeoPandas and QGIS",[10,1094,1095],{},"Once you have coordinates and geometry, the SDK is out of the way and the output flows into standard tools. GeoPandas reads the GeoJSON directly, and from there you can reproject, spatially join, or write a Shapefile:",[32,1097,1099],{"className":68,"code":1098,"language":70,"meta":37,"style":37},"import geopandas as gpd\n\ngdf = gpd.read_file(\"parcels.geojson\")\ngdf = gdf.set_crs(\"EPSG:4326\")\ngdf.to_file(\"parcels.shp\")\n",[39,1100,1101,1114,1118,1143,1168],{"__ignoreMap":37},[42,1102,1103,1105,1108,1111],{"class":44,"line":45},[42,1104,78],{"class":77},[42,1106,1107],{"class":81}," geopandas ",[42,1109,1110],{"class":77},"as",[42,1112,1113],{"class":81}," gpd\n",[42,1115,1116],{"class":44,"line":85},[42,1117,103],{"emptyLinePlaceholder":102},[42,1119,1120,1123,1125,1128,1130,1133,1135,1137,1139,1141],{"class":44,"line":99},[42,1121,1122],{"class":81},"gdf ",[42,1124,113],{"class":112},[42,1126,1127],{"class":81}," gpd",[42,1129,133],{"class":120},[42,1131,1132],{"class":116},"read_file",[42,1134,121],{"class":120},[42,1136,144],{"class":143},[42,1138,1049],{"class":52},[42,1140,144],{"class":143},[42,1142,185],{"class":120},[42,1144,1145,1147,1149,1152,1154,1157,1159,1161,1164,1166],{"class":44,"line":106},[42,1146,1122],{"class":81},[42,1148,113],{"class":112},[42,1150,1151],{"class":81}," gdf",[42,1153,133],{"class":120},[42,1155,1156],{"class":116},"set_crs",[42,1158,121],{"class":120},[42,1160,144],{"class":143},[42,1162,1163],{"class":52},"EPSG:4326",[42,1165,144],{"class":143},[42,1167,185],{"class":120},[42,1169,1170,1173,1175,1178,1180,1182,1185,1187],{"class":44,"line":155},[42,1171,1172],{"class":81},"gdf",[42,1174,133],{"class":120},[42,1176,1177],{"class":116},"to_file",[42,1179,121],{"class":120},[42,1181,144],{"class":143},[42,1183,1184],{"class":52},"parcels.shp",[42,1186,144],{"class":143},[42,1188,185],{"class":120},[10,1190,1191,1192,1196],{},"The Shapefile opens in QGIS or ArcGIS as parcel boundaries you can style, label, and overlay on imagery. Because the geometry is the BLM survey boundary and not a 640-acre square drawn around a centroid, the parcels line up with adjacent tracts the way the survey record does. That accuracy matters most where sections are irregular, which is common along survey correction lines and near water boundaries. For async pipelines, GeoPandas patterns at scale, and other production notes, the ",[61,1193,1195],{"href":1194},"\u002Fguides\u002Fpython-sdk-advanced","Python SDK advanced guide"," goes deeper than this walkthrough.",[14,1198,1200],{"id":1199},"python-sdk-typescript-sdk-or-the-rest-api-directly","Python SDK, TypeScript SDK, or the REST API directly",[10,1202,1203],{},"The three access paths serve different contexts, so the right one depends on where the code runs.",[1205,1206,1207,1215,1221],"ul",{},[1208,1209,1210,1214],"li",{},[1211,1212,1213],"strong",{},"Use the Python SDK"," when the work lives in the Python data stack: a GeoPandas or GDAL pipeline, an ArcPy script, a notebook, or a scheduled ETL job. This is the natural fit for GIS and data science teams.",[1208,1216,1217,1220],{},[1211,1218,1219],{},"Use the TypeScript SDK"," when the conversion happens in a web app, a serverless function, or a Node service. It gives the same endpoints with types suited to that runtime.",[1208,1222,1223,1226],{},[1211,1224,1225],{},"Call the REST API directly"," when you work in a language without an SDK, or when a single lookup does not justify a dependency. A plain HTTPS request to the same endpoints returns the same JSON.",[10,1228,1229,1230,1234,1235,1239],{},"All three read from one API and one BLM-derived dataset, so results are consistent whichever you choose. The batch endpoint that powers this pipeline is metered per record on the API Build, Scale, and Enterprise tiers; see the ",[61,1231,1233],{"href":1232},"\u002Fpricing","API pricing"," and the ",[61,1236,1238],{"href":1237},"\u002Findustries\u002Fdevelopers","developer overview"," for current limits.",[10,1241,1242,1243,1247],{},"If your input mixes Texas tracts with PLSS descriptions, the same API resolves the Texas Abstract, Block, and Survey grid alongside PLSS, so a pipeline covering both survey systems does not need a second source. Start with the ",[61,1244,1246],{"href":1245},"\u002Fguides\u002Fpython-sdk","Python SDK quick start",", point it at a real column of legal descriptions, and let the coordinates land in the format your GIS already reads.",[1249,1250,1251],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sFwrP, html code.shiki .sFwrP{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#24292E;--shiki-default-font-style:inherit;--shiki-dark:#E1E4E8;--shiki-dark-font-style:inherit}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":37,"searchDepth":85,"depth":85,"links":1253},[1254,1255,1256,1257,1258],{"id":16,"depth":85,"text":17},{"id":26,"depth":85,"text":27},{"id":278,"depth":85,"text":279},{"id":1091,"depth":85,"text":1092},{"id":1199,"depth":85,"text":1200},"\u002Fimages\u002Fblog\u002Ftownship-america-python-sdk-plss-lookups-in-python-for-gis-scripts-and-spatial-data-pipelines\u002Fcover.webp","2026-09-03T10:00:00Z","Use the PLSS Python SDK to geocode legal descriptions in Python. Batch convert a CSV to coordinates and GeoJSON polygons, then load the result into GeoPandas and QGIS.",false,"md",{"author":1265,"category":1266},"Township America","guides","\u002Fblog\u002Ftownship-america-python-sdk-plss-lookups-in-python-for-gis-scripts-and-spatial-data-pipelines",{"title":5,"description":1261},"blog\u002Ftownship-america-python-sdk-plss-lookups-in-python-for-gis-scripts-and-spatial-data-pipelines",[70,1271,1272,1273,1274],"sdk","plss","gis","geopandas","zcIr9Ed_7ea71Ii3ic9nSyd8yM3kJ83PLvWev4-OE4A"]