FEMA maintains a list of downloadable NFHL shapefiles for thousands of U.S. counties and communities. However, it can be difficult to get just the data you need. Perhaps you want data for a specific spatial region, by date, FIRMID, community ID or FIPS. This GDAL pipeline one-liner can be run nightly to maintain an up-to-date queryable data base in GeoPackage (GPKG) format. It takes about 5 minutes run, resulting in a GeoPackage file named nfhl.gpkg.
Now that you've added the script to cron and run it once, let's see what we can do with the nfhl.gpkg. The following attributes are available:
Let's talk about the NFHL shapefiles, briefly. There are over 2,600 and growing. Each file has multiple layers, they are:
S_BASE_INDEX, S_BFE, S_CST_GAGE, S_CST_TSCT_LN, S_DATUM_CONV_PT, S_FIRM_PAN, S_FLD_HAZ_AR, S_FLD_HAZ_LN, S_GEN_STRUCT, S_LABEL_LD, S_LABEL_PT, S_LIMWA, S_LOMR, S_PFD_LN, S_PLSS_AR, S_POL_AR, S_PROFIL_BASLN, S_STN_START, S_SUBBASINS, S_SUBMITTAL_INFO, S_TSCT_BASLN, S_WTR_AR, S_WTR_LN, S_XS, L_COMM_INFO, L_COMM_REVIS, L_CST_MODEL, L_CST_TSCT_ELEV, L_MANNINGSN, L_MEETINGS, L_MT2_LOMR, L_MTG_POC, L_PAN_REVIS, L_SOURCE_CIT, L_SUMMARY_DISCHARGES, L_XS_ELEV, STUDY_INFO
What each of those layers have in common is a DFIRM_ID attribute. This identifies its unique area. Typically, the DFIRM_ID is the state/county FIPS, with a 'C' appended. Example: 06015C which is Del Norte County, CA. In some cases the DFIRM_ID will not have that pattern, it will be a community id, like 230025, which is the Town of Mapleton.
The key to remember is, DFIRM_ID is how you associate all the layers.
Let's try a few examples extracting meta-data from nfhl.gpkg, building a query and finally downloading/aggregating our request direct from FEMA into a single file.
NFHL county/community data for all of Florida updated within the last 7 days# Get NFHL data for Florida, using state FIPS 12, last 7 days, save as a GeoPackage gdal vector concat --mode merge-per-layer-name \ $(gdal vector sql --dialect sqlite --sql "select vsiurl from nfhl where stcofips like '12%' and updated between 20260505 and 20260512" -i nfhl.gpkg --lco HEADER=NO --of CSV -o /vsistdout/) \ florida_$(date +%Y%m%d).gpkg --overwriteNFHL county/community data for all of Rhode Island (or any state)
Rhode Island state FIPS is 44. Build the query, download all shape files and save as a single GeoPackage, with all the above layers:
gdal vector pipeline \
! concat --mode merge-per-layer-name \
$(gdal vector sql -i nfhl.gpkg -o /vsistdout/ --dialect sqlite --sql "select group_concat(vsiurl, ' ') from nfhl where stcofips like '44%'" --lco HEADER=NO --of CSV) \
! write -o rhodeIsland_20260512.gpkg --overwrite
Single County Download by State/County FIPS
# Get NFHL data for Charlotte County Florida, using state/county FIPS 12015, save as a GeoPackage gdal vector convert \ -i $(gdal vector sql --dialect sqlite --sql "select vsiurl from nfhl where stcofips = '12015'" -i nfhl.gpkg --lco HEADER=NO --of CSV -o /vsistdout/) \ -o charlotte.gpkg --overwriteGetting all DFIRM_ID's, community ids, locales and data url's for a State/County FIPS '08117', which is Summit County, Colorado
gdal vector sql \
-i nfhl.gpkg --dialect sqlite \
--sql "select \
group_concat(dfirmid, ',') as dfirmids, group_concat(cids, ', ') as cids, group_concat(locale,', ') as locales, group_concat(url, ' ') as urls \
from nfhl where stcofips = '08117'" \
-o /vsistdout/ --of CSV --lco STRING_QUOTING=ALWAYS
Which returns the CSV:
"dfirmids","cids","locales","urls"
"08117C","080016,080072,080172,080201,080237,080245,080290","SUMMIT COUNTY","https://hazards.fema.gov/femaportal/NFHL/Download/ProductsDownLoadServlet?DFIRMID=0&state=s&county=c&fileName=08117C_20240512.zip"
Alternatively, you could change where stcofips = '08117' to where stcofips like '08%' to get all the NFHL county/community data for the entire state of Colorado.
GDAL Pipeline One-Liner
#!/usr/bin/env bash # Download FEMA polygons for all county and community level data, cl2024_v1.gdb.zip. Do this infrequently. [[ ! -f "cl2024_v1.gdb.zip" ]] \ && wget -q -T 30 -t 3 https://www.fema.gov/about/reports-and-data/openfema/cl2024_v1.gdb.zip
# Create a list of DFIRMID's directly from NFHL data links, save as firm.gpkg. Do this once daily. [[ ! -f "firm.gpkg" ]] \ && echo -e "dfirmid,updated,locale\n""$(wget -q 'https://hazards.fema.gov/femaportal/NFHL/searchResult' -O- \ | sed -n 's/.*ounty=\(.*\)&fileName=\(.*\)_\(.*\)\.zip.*/"\2","\3","\1"/p' |tr -s '"')" \ | gdal vector convert -i /vsistdin/ --if CSV -o firm.gpkg --output-layer firm --overwrite
# Create nfhl.gpkg. Do this once daily.
gdal vector pipeline \
concat firm.gpkg [ \
! read cl2024_v1.gdb.zip --input-layer NfipCommunityLayerNoOverlapsWhole \
! clip --bbox -126,24,-66,50 --bbox-crs EPSG:4326 \
! reproject --dst-crs EPSG:4326 \
! rename-layer --output-layer comm \
! sql --dialect sqlite --output-layer comm --sql "select cis_cid, county_fips as stcofips, county_fips||'C' as firmid, shape as geom from comm" \
] \
! write -o community.gpkg --overwrite \
&& gdal vector pipeline \
! read -i community.gpkg \
! sql --dialect sqlite --sql "$(cat<<EOF
select
f.dfirmid, c.stcofips, cast(f.updated as integer) as updated, f.locale, group_concat(c.cis_cid,',') as cids
,'https://hazards.fema.gov/femaportal/NFHL/Download/ProductsDownLoadServlet?DFIRMID=0&state=s&county=c&fileName='||f.dfirmid||'_'||f.updated||'.zip' as url
,st_union(c.geom) as geom
from firm f
join comm c on f.dfirmid = c.firmid or f.dfirmid = c.cis_cid
group by f.dfirmid, c.stcofips, f.updated, f.locale
EOF
)" \
! simplify-coverage --tolerance .0001 \
! set-geom-type --multi \
! write -o nfhl.gpkg --output-layer nfhl --overwrite
# rm firm.gpkg
That's it!