MMQGIS

Michael Minn (http://michaelminn.com)

11 August 2019

Describes use of MMQGIS, a set of Python vector map layer plugins for Quantum GIS

Introduction

MMQGIS is a set of Python plugins for manipulating vector map layers in Quantum GIS: CSV input/output/join, geocoding, geometry conversion, buffering, hub analysis, simplification, column modification, and simple animation. MMQGIS provides verbose progress reporting, an intuitive user interface, direct file access, and some additional capabilities missing from other plugin sets such as the Processing toolbox.

Getting MMQGIS

MMQGIS is included in the Quantum GIS Plugin Repository and should be readily available in the QGIS Python Plugin Installer (Plugins -> Fetch Python Plugins). A zip file of the current release is also available here for manual installation. Links to older versions are listed in the Version History at the bottom of this page.

Data Formats

Input layers can be from any geospatial data source supported by QGIS.

Output file format are implied by the file extension given on the output file name. Formats currently supported through OGR/GDAL include:

MMQGIS assumes that input and output files are encoded in the UTF-8 character set. MMQGIS uses the standard Python CSV file interface functions, which do not handle unicode or other multi-byte encodings. While files that use the lower 7-bits of the 8-bit Windoze character sets (ISO-8859-x) will generally be fine, unpredictable results and errors may occur with non-ASCII characters in non-UTF-8 character sets. See this and this for two ways to save a UTF-8 CSV from Excel.

Python Scripting with MMQGIS

MMQGIS contains a library module (mmqgis_library.py) that can also be used for Python scripting from the QGIS console, or in stand-alone scripts executed directly in Python. API information is included with the descriptions for each tool, and a guide to scripting is at the end of this document.

Licensing

MMQGIS is free software and is offered without guarantee or warranty. You can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License (GPL v2) as published by the Free Software Foundation (www.gnu.org). Bug reports or suggestions are welcome at the e-mail address above, but I cannot promise a prompt or mutually satisfactory resolution.

Animation Tools

The animation tools create simple map animations as sequences of map image PNG files.

Images are exported from print layouts so at least one print layout must be defined. Map layouts permit adjustment of map extent, size, and DPI, as well as the ability to add additional cartographic elements to the output images.

Images may be combined into a single animated GIF using Gimp. This is the technique used for the example GIF files below.

Images may also be combine into video files using FFmpeg. For example, this creates a 30 FPS silent MPEG4 file using an h.264 codec that can be played in contemporary web browsers:

 ffmpeg -r 30 -s 1280x720 -start_number 0 -i frames/frame%04d.png -vcodec libx264 -crf 1 -pix_fmt yuv420p temp.mp4

Animate Lines

Animated branches of the Long Island Railroad (2019)

The Animate Lines tool creates animation of lines that grow to their full length over the duration of the animation. This is primarily useful as a visual effect.

The direction of growth is determined by the order of the linestring points in the source file. Depending on how the lines were digitized, this direction may be opposite of what is desired. To control the direction of growth, line vertices in the desired order can be created in a CSV file and imported with the Geometry Import From CSV File.

If attempting to animate a layer of MultiLineStrings, this tool will combine the points from all line segments into a single line of points. This will add spurious line segments to the animation if the points in the line segments were not ordered in a contiguous manner when digitized.

Parameters

Animate Lines Dialog

Python

mmqgis_animate_lines(print_layout, input_layer, fixed_speed, frame_count, \
			frame_directory, status_callback = None)

The example below animates branch lines of the Long Island Railroad in New York State.

# Paths to the project
test_directory = "/home/michael/software/mmqgis/tests/animate-lines"

# Remove any existing frames and create the frame directory
frame_directory = test_directory + "/frames"
shutil.rmtree(frame_directory, ignore_errors=True)
os.mkdir(frame_directory)

# Load the project
project_file = test_directory + "//2019-lirr-branches.qgs"
project = QgsProject.instance()
project.read(project_file)
if project.error():
	exit("Error: " + project.error())

# Find the parameters
print_layout = QgsProject.instance().layoutManager().printLayouts()[0]
layer = project.mapLayersByName("2019-lirr-branches")[0]
fixed_speed = True
frame_count = 20

# Run the tool and display any resulting error
message = mmqgis_animate_lines(print_layout, layer, fixed_speed, frame_count, frame_directory)
print("Animate lines: " + str(message))

Animate Location

Birthplaces of the Roster Players on AL Division Champion Teams in 2019

The Animate Location tool creates animations of map features as they move in a straight line to specified locations on separate destination map layer.

If you need more control over paths, you should use the Animate Sequence tool, or Anita Graser's TimeManager plugin.

This tool can be slow and processing-intensive if animating a large number of features.

Parameters

Animate Location Dialog

Python

mmqgis_animate_location(print_layout, source_layer, source_key_field, \
		destination_layer, destination_key_field, frame_count, \
		frame_directory, status_callback = None)

The example below animates the movement of the 134 players on the rosters of the three Major League Baseball teams that won the three divisions of the American League in 2019 from their birthplaces to the cities where their championship teams play. Data from BaseballAlmanac.com and Baseball-Reference.com. Geocoded with Nominatim. Ballpark data from Wikipedia.

test_directory = "/home/michael/software/mmqgis/tests/animate-location"

# Load the project
# This needs to be a full path, otherwise layers do not get read in
project_file = test_directory + "/2018-al-division-champion-players.qgs"
project = QgsProject.instance()
project.read(project_file)
if project.error():
	exit("Error: " + project.error())

# Create the frame directory
frame_directory = test_directory + "/frames"
shutil.rmtree(frame_directory, ignore_errors=True)
os.mkdir(frame_directory)

# Parameters
print_layout = QgsProject.instance().layoutManager().printLayouts()[0]
source_layer = project.mapLayersByName("2018-al-division-champion-players")[0]
source_key_field = "Team"
destination_layer = project.mapLayersByName("2019-mlb-ballparks")[0]
destination_key_field = "Team"
frame_count = 10

# Run the function and print any error message
message = mmqgis_animate_location(print_layout, source_layer, source_key_field, \
		destination_layer, destination_key_field, frame_count, frame_directory)

print("Animate Location: " + str(message))

Animate Sequence

A Trip Across New York Harbor on the Staten Island Ferry on 30 December 2009

The Animate Sequence tool creates animations of map features in one or more layers by plotting successive rows in each layer.

If multiple layers are being animated simultaneously, the timing of rows in each layer will be the same. There is no capability for interpolating, frame skipping or frame duplication between layers of dissimilar length. If there are fewer rows in one animated layer than in another animated layer, no features from the shorter layer will be displayed after all rows in the shorter layer have been displayed.

Animate Sequence uses an edit buffer to sequentially display individual feature geometries. Therefore animated layers must be editable. If you wish to animate a non-editable layer (such as GPS waypoints from a GPX or CSV file), you should save the layer to an editable source, such as a shapefile, and animate the editable layer.

If text display of timing is desired, a layer must be created with a column that has the desired display text, and that layer must be mapped with features labeled.

If you need fine-grained control by specific times or a less cumbersome way of displaying current time, you may want to consider using Anita Graser's TimeManager plugin.

Parameters

Animate Sequence Form

Python

mmqgis_animate_sequence(print_layout, layers, cumulative, frame_directory, status_callback = None)

The example below animates sampled GPS waypoints tracking a trip on the Staten Island Ferry from St. George to South Ferry on 30 December 2009.

test_directory = "/home/michael/software/mmqgis/tests/animate-rows/si-ferry"

# Load the project file
project_file = test_directory + "/animate-sequence.qgs"
project = QgsProject.instance()
project.read(project_file)
if project.error():
	exit("Error: " + project.error())

# Create the frame directory
frame_directory = test_directory + "/frames"
shutil.rmtree(frame_directory, ignore_errors=True)
os.mkdir(frame_directory)

# Parameters
print_layout = QgsProject.instance().layoutManager().printLayouts()[0]
layers = project.mapLayersByName("2009-12-30-si-ferry-sampled")
cumulative = False

# Run the tool
message = mmqgis_animate_sequence(print_layout, layers, cumulative, frame_directory)
print("Animate Sequence: " + str(message))

Animate Zoom

A zoom out from NYC

The Animate Zoom tool creates animations by zooming and or panning. This tool is especially useful for simulating motion across a landscape or zooming out to give context to a location.

Parameters

Animate Zoom Form

Python

mmqgis_animate_zoom(print_layout, start_lat, start_long, start_zoom, \
		    end_lat, end_long, end_zoom, duration_frames, \
		    frame_directory, status_callback = None)

The example animates a zoom out on the USGS National Map ImageryOnly Base Map from NYC's Empire State Building to a whole-country view. Note that the USGS servers can be slow, so this script will take a few minutes to run even with a fairly small number of frames.

# US National Map: USGS ImageryOnly Base Map - Primary Tile Cache
# https://viewer.nationalmap.gov/services/
map_url = "crs=EPSG:3857&dpiMode=7&format=image/png&layers=0&styles&" + \
	"url=https://basemap.nationalmap.gov/arcgis/services/USGSImageryOnly/MapServer/WMSServer"
map_layer = QgsRasterLayer(map_url, "US National Map Imagery", 'wms')

# Create the print layout with a single page for a 1280x720 image at 300 DPI
print_layout = QgsPrintLayout(QgsProject.instance())
print_layout.initializeDefaults()
page_size = QgsLayoutSize(108.4, 61, QgsUnitTypes.LayoutMillimeters);
print_layout.pageCollection().page(0).setPageSize(page_size)

# Create a map item covering the whole page
map_item = QgsLayoutItemMap(print_layout)
map_item.setFixedSize(page_size)
map_item.attemptMove(QgsLayoutPoint(0, 0))
map_item.setCrs(map_layer.crs())
map_item.setExtent(QgsRectangle(-100000, -100000, 100000, 100000)) # arbitrary = tool changes this
map_item.setBackgroundColor(QColor("#2a578e")) # Ocean blue
map_item.setBackgroundEnabled(1)
map_item.setLayers([ map_layer ])

# Add the item to the layout
print_layout.addLayoutItem(map_item)

# Parameters
start_lat = 40.748481
start_long = -73.985707
start_zoom = 15
end_lat = 40.748481
end_long = -73.985707
end_zoom = 3
frame_count = 20
frame_directory = "/home/michael/software/mmqgis/tests/animate-zoom/frames"

message = mmqgis_animate_zoom(print_layout, start_lat, start_long, \
	start_zoom, end_lat, end_long, end_zoom, frame_count, \
	frame_directory, status_callback)

print("Animate Zoom: " + str(message))

Combine Tools

Attribute Join from CSV File

The Join Attributes tool combines attributes from a CSV file with an existing vector layer by performing a join using a "key" field that is present both in the CSV file and in the attribute table of the map layer to which the data is being joined.

Because CSV files contain no reliable type data in the header, data is always imported from CSV files as text. Text columns can be converted to numeric (floating point) using the "Text to Float" tool.

CSV files must be encoded in the UTF-8 character set. Although other 8-bit encodings (like Windoze ISO-8859-x) will work if only ASCII characters are present, non-ASCII characters may cause unpredictable behavior.

This join is an SQL FULL OUTER JOIN. If there are multiple occurances of a key in the CSV or map layer, there will be multiple combinations of the data in the output shapefile.

QGIS has the ability to perform table joins as part of projects. If you only need joined data for a specific project and/or the underlying data changes periodically, you may prefer to use table joins. Right click on the layer and select Properties -> Joins.

Parameters

Attribute Join Dialog

Python

mmqgis_attribute_join(input_layer, input_layer_attribute, \
		input_csv_name, input_csv_attribute, \
		output_file_name, status_callback = None)

The example below joins a USCB state cartographic boundary shapefile with 2017 CDC data on chlamdia, gonorrhea, and syphilis rates by state.

input_layer_name = "cb_2018_us_state_500k.shp"
input_layer = QgsVectorLayer(input_layer_name)
input_layer_attribute = "NAME"
input_csv_name = "2017-std-states.csv"
input_csv_attribute = "State Name"
output_file_name = "2017-std-states.geojson"

message = mmqgis_attribute_join(input_layer, input_layer_attribute,
		input_csv_name, input_csv_attribute, output_file_name)
print("Attribute Join: " + str(message))

Merge Layers

The Merge Layers tool merges features from multiple layers into a single shapefile and adds the merged shapefile to the project.

Merged layers must all be the same geometry type (point, polygon, etc.).

If the source layers have different attribute fields (distinguished by name and type), the merged file will contain a set of all different fields from the source layers with NULL values inserted when a source layer does not have a specific output field.

Where fields with the same name in different layers have different types, the output field type will be string.

Parameters

Merge Dialog

Python

mmqgis_merge(input_layers, output_file_name, status_callback = None)

The example below merges four regional data files into a single layer.

input_layer_1 = QgsVectorLayer("west.shp")
input_layer_2 = QgsVectorLayer("northeast.shp")
input_layer_3 = QgsVectorLayer("midwest.shp")
input_layer_4 = QgsVectorLayer("dixie.shp")
input_layers = [input_layer_1, input_layer_2, input_layer_3, input_layer_4]

output_file_name = "united-states.geojson"

message = mmqgis_merge(input_layers, output_file_name, status_callback)
print("Merge States: " + str(message))

Spatial Join

The Spatial Join tool combines attributes from different layers based on spatial relationship. The spatial joins are very common operations in standard GIS analysis.

A COUNT attribute is automatically appended to the output that indicates the number of related join layer features contributing data to each feature from the target layer.

Features from the target layer that have no spatial relationship to the join layer are not included in the output.

Parameters

Spatial Join Dialog

Python

mmqgis_spatial_join(target_layer, spatial_operation, join_layer, field_names, field_operation, \
	output_file_name, status_callback = None)

The example below joins nuclear reactor locations from Wikipedia and the NRC with county-level income data from the 2013-2017 American Community Survey to get the average median household income of counties containing nuclear reactors.

target_layer_name = "2018-nuclear-plants.geojson"
target_layer = QgsVectorLayer(target_layer_name)
spatial_operation = "Intersects"
join_layer_name = "2017-acs-counties.geojson"
join_layer = QgsVectorLayer(join_layer_name)
field_names = ["Median Household Income", "Median Household Income MOE", "Total Population", "County Name"]
field_operation = "First"
output_file_name = "2018-nuclear-counties.geojson"

message = mmqgis_spatial_join(target_layer, spatial_operation, join_layer, \
	field_names, field_operation, output_file_name, status_callback)
print("Spatial Join: " + str(message))

# Calculate average household income in nuclear counties weighted by population
moe = 0
income = 0
total_population = 0
output_layer = QgsVectorLayer(output_file_name)
for feature in output_layer.getFeatures():
	population = int(feature.attribute("Total Population"))
	moe = moe + (population * float(feature.attribute("Median Household Income MOE")))
	income = income + (population * float(feature.attribute("Median Household Income")))
	total_population = total_population + population

moe = moe / total_population
income = income / total_population
print("Nuclear counties: population " + str(total_population) + ", average income $" \
	+ str(income) + " +/- $" + str(moe))

Create Tools

Create Buffers

Half-Mile Buffers Around North Shore Railroad Stations

The Create Buffers tool creates polygon buffers around points, lines and polygons.

This tool expresses buffer sizes as absolute linear distance units (miles, feet, meters, kilometers defined by WGS 84 great circle distance) rather than map units. The haversine formula is used to approximate meaningful radius distances independent of the original projection. While this may introduce some deviation from the original CRS, buffering is assumed in practice to be an inexact operation that can usually tolerate such discrepancy.

Buffer radius can be fixed for all buffers as specified in the dialog, or buffer sizes can be taken from an attribute in the source shape layer.

Point buffers can be created with different numbers of edges: triangles, diamonds, pentagons, hexagons, 32-edge circles or 64-edge circles. If other numbers of edges, or different numbers of edges for different points are needed, the number of edges can be taken from a numeric attribute in the source shape layer.

Point buffers can be rotated based on a fixed number of degrees, or based on a numeric attribute in the source shape layer that indicates the rotation for each individual feature. The first node of a point buffer at zero degrees rotation is directly north from the point being buffered.

Line buffers can be created as normal rounded-end, single-sided, or flat-ended. Flat ended buffers are created by dissolving a north-sided and south-sided buffer for each line. This may cause odd shapes when line ends point in directions that deviate significantly from the general direction of the line. With multiple-line features, each line segment is treated as a separate shape, so angular deviations between line segments may result in slivers.

Line buffers can also be north-, south-, east- or west-side only. The determination of which side of the line is buffered is determined by the angular direction of line as defined by the start and end points of the line. This may result in undesirable results if buffering layers that mix vertically- and horizontally-oriented features. This tool does not currently have the capability to buffer based on directional characteristics like traffic or water flow through the features that lines are used to represent. For lines that are exactly perpendicular, the west side is considered the north side for north side buffering. For lines that are exactly horizontal, the south side is considered the west side.

Parameters

Create Buffers Form

Python

mmqgis_buffers(input_layer, selected_only, radius_attribute, radius, radius_unit, \
	edge_attribute, edge_count, rotation_attribute, rotation_degrees, \
	output_file_name, status_callback = None)

The example below creates half-mile buffers around the stations on the abandoned North Shore Branch of the Staten Island Railway. Passenger service terminated on this line in 1953, and freight service terminated in 1989, although there is interest in reactivating the line as light rail or bus rapid transit.

input_file_name = "2010-nsrr-stations.geojson"
input_layer = QgsVectorLayer(input_file)
selected_only = False
radius_attribute = None
radius = 0.5
radius_unit = "Miles"
edge_attribute = None
edge_count = 4
rotation_attribute = None
rotation_degrees = 0
output_file_name = "2010-nsrr-buffers.geojson"

message = mmqgis_buffers(input_layer, selected_only, radius_attribute, radius, radius_unit, \
	edge_attribute, edge_count, rotation_attribute, rotation_degrees, output_file_name)

print("Point Buffers: " + str(message))

Create Grid Layer

One-Mile Grid over Manhattan

The Grid tool creates a layer containing a grid of shapes.

Parameters

Create Grid Form

Python

mmqgis_grid(geometry_type, crs, x_spacing, y_spacing, \
	x_left, y_bottom, x_right, y_top, \
	output_file_name, status_callback = None)

The example below creates a ten-degree WGS 84 grid for the whole earth.

geometry_type = "Lines"
crs = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")
x_spacing = 10
y_spacing = 10
x_left = -170
x_right = 170
y_bottom = -80
y_top = 80
output_file_name = "world-grid.geojson"

message = mmqgis_grid(geometry_type, crs, x_spacing, y_spacing, \
	x_left, y_bottom, x_right, y_top, output_file_name)

print("Create Grid: " + str(message))

Hub Lines / Distance

Core-based Statistical Areas (spokes) Matched To Their Closest MBL Ballparks (hubs)

The Hub Lines / Distance tool creates spoke lines from spoke end points to hub points. Such layers are useful for visualizing catchment or distribution areas, or estimating travel distance.

The hub lines added to the output file with all attributes from the associated spoke points.

This tool does not incorporate any kind of network analysis, so if real-life paths to hubs are non-linear (e.g. when dealing with city blocks and other types of transportation networks), the closest great-circle or Euclidean distance may not be the closed in terms of traveling distance.

Parameters

Hub Lines Form

Python

mmqgis_hub_lines(hub_layer, hub_name_field, spoke_layer, spoke_hub_name_field, \
	allocation_criteria, distance_unit, output_geometry, \
	output_file_name, status_callback = None)

The following example creates hubs between the centroids of major core-based statistical areas (metropolitan areas) and major league baseball parks to estimate geographic allegiance to MLB teams.

hub_layer_name = "2019-mlb-ballparks.geojson"
hub_layer = QgsVectorLayer(hub_layer_name)
hub_name_field = "Team"
spoke_layer_name = "cb_2018_us_cbsa_5m.shp"
spoke_layer = QgsVectorLayer(spoke_layer_name)
spoke_name_field = "Name"
allocation_criteria = "Nearest Hub"
distance_unit = "Miles"
output_geometry = "Lines to Hubs"
output_file_name = "2019-cbsa-mlb-team.geojson"

message = mmqgis_hub_lines(hub_layer, hub_name_field, spoke_layer, spoke_name_field, \
	allocation_criteria, distance_unit, output_geometry, output_file_name)
print("Nearest Hubs: " + str(message))

Voronoi Diagram

Voronoi Diagram of MLB Ballparks

The Voronoi Diagram tool creates Voronoi diagrams, which are collections of polygons, each surrounding individual points and representing the area around each point that is closer to that point than any other. The shapes are named after Russian mathematician Georgy Fedoseevich Voronoi (1868-1908), who published a formal definition in 1908, but the concept of this type of polygon extends back to Descartes in the 17th century. Similar polygons were famously used by physician John Snow to trace the source of a London cholera epidemic in 1854 and Voronoi diagrams remain useful in GIS for analyzing areas of influence associated with individual points within a collection. Voronoi diagrams are also called Thiessen polygons, named after their usage in meteorology by Alfred Thiessen.

This tool requires an input point layer and outputs a polygon layer.

The boundary of the Voronoi diagram is the min/max extent of the points in the source layer.

The algorithm used to calculate the edges and nodes starts with tangents at the midpoints of lines between each point. The closest tangent is assumed to be a border and intersections to the border are calculated to circle each border until back to the beginning. The algorithm is computationally intensive, but still seems to run fairly quickly on a reasonably small set of points.

Parameters

Voronoi Form

Python

mmqgis_voronoi_diagram(input_layer, output_file_name, status_callback = None)

The following example creates Voronoi polygons around major league baseball parks.

input_layer_name = "2019-mlb-ballparks.geojson"
input_layer = QgsVectorLayer(input_layer_name)
output_file_name = "2019-mlb-ballparks-voronoi.geojson"
message = mmqgis_voronoi_diagram(input_layer, output_file_name, status_callback)
print("Voronoi Diagram: " + str(message))

Geocode Tools

Geocode CSV With Web Service

The Geocode CSV With Web Service tool imports addresses from a CSV file and uses a web geocoding service to geocode addresses to a point output file.

The web service tools use the Python urllib to make https requests to the respective geocoding APIs. If your system does not have the certificate for the API server, you may get a urlopen error [ SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed error message. Although this issue can have numerous causes (and solutions), you can start to diagnose the problem by accessing the API URL in a web browser window to see if the API is accessible from your system. In some cases, simply doing this will load the certificate and solve the problem with MMQGIS.

Additional attributes are added to each feature to preserve what the web service geocoded so that accuracy can be assessed. These attributes are dependent upon the geocoder used:

Parameters

Geocode Web Service Form

Python

mmqgis_geocode_web_service(input_csv_name, parameter_attributes, web_service,
	api_key, use_first, output_file_name, not_found_file_name, status_callback = None)

parameter_attributes is a list of tuples mapping fields in the CSV file to one of the following address fields: Address, City, State, Country.

The following example geocodes the 12 US Federal Reserve Banks using Nominatim.

input_csv_name = "2019-federal-reserve-banks.csv"
parameter_attributes = [ ("Address", "Street"), ("City", "City"), ("State", "State") ]
web_service = "OpenStreetMap / Nominatim"
api_key = None
use_first = True
output_file_name = "2019-federal-reserve-banks.geojson"
not_found_file_name = "2019-federal-reserve-banks.notfound.csv"

message = mmqgis_geocode_web_service(input_csv_name, \
	parameter_attributes, web_service, api_key, use_first, \
	output_file_name, not_found_file_name)

print("Geocode: " + str(message))

The following example geocodes the same locations using the Google geocoder.

input_csv_name = "2019-federal-reserve-banks.csv"
parameter_attributes = [ ("Address", "Street"), ("City", "City"), ("State", "State") ]
web_service = "Google"
api_key = "YOUR API KEY HERE"
use_first = True
output_file_name = "2019-federal-reserve-banks.geojson"
not_found_file_name = "2019-federal-reserve-banks.notfound.csv"

message = mmqgis_geocode_web_service(input_csv_name, \
	parameter_attributes, web_service, api_key, use_first, \
	output_file_name, not_found_file_name)

print("Geocode: " + str(message))

The following example geocodes the same locations using the ArGIS World Geocoding Service.

input_csv_name = "2019-federal-reserve-banks.csv"
parameter_attributes = [ ("Address", "Street"), ("City", "City"), ("State", "State") ]
web_service = "ESRI Server"
api_key = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates"
use_first = True
output_file_name = "2019-federal-reserve-banks.geojson"
not_found_file_name = "2019-federal-reserve-banks.notfound.csv"

message = mmqgis_geocode_web_service(input_csv_name, \
	parameter_attributes, web_service, api_key, use_first, \
	output_file_name, not_found_file_name)

print("Geocode: " + str(message))

Geocode from Street Layer

Street Layer Geocoding Parameters

The Geocode Street Layer tool geocodes addresses from a CSV file using an address locator layer with street centerline features and attributes indicating the range of addresses associated with each feature.

Examples of street centerline files include:

As with many tools of this type, the matching and interpolation is quite fragile. Street names in the street layer generally must match the names in the CSV file exactly - First Street may not match 1st Street.

The tool will also only handle address street numbers that are entirely numeric. Hyphenated (172-10 Seventh Ave) or fractional nubers (1872a Main) will generally not match, although line files for areas with hyphenated numbers will often have a field with the hyphens removed.

To improve matching, street names are internally modified with common abbreviations such as st for street and w for west. This add fuzziness to the search process that improves the hit ratio but may result in unexpected results. This address handling may be augmented or refined in future releases.

Parameters

Street Layer Geocode Form

Python

mmqgis_geocode_street_layer(input_csv_name, number_column, street_name_column, zip_column, \
	input_layer, street_name_attr, left_from_attr, left_to_attr, left_zip_attr, \
	right_from_attr, right_to_attr, right_zip_attr, \
	from_x_attr, from_y_attr, to_x_attr, to_y_attr, setback, \
	output_file_name, not_found_file, status_callback = None)

The following example geocodes a file of addresses in Washington, DC using a US Census Bureau TIGER/Line All Lines shapefile.

input_csv_name = "washington-locations.csv"
number_column = "Number"
street_name_column = "Street"
zip_column = "ZIP"

input_layer_name = "tl_2018_11001_edges.shp"
input_layer = QgsVectorLayer(input_layer_name)
street_name_attr = "FULLNAME"
left_from_attr = "LFROMADD"
left_to_attr = "LTOADD"
left_zip_attr = "ZIPL"
right_from_attr = "RFROMADD"
right_to_attr = "RTOADD"
right_zip_attr = "ZIPR"

from_x_attr = None
from_y_attr = None
to_x_attr = None
to_y_attr = None
setback = 0.0001

output_file_name = "washington.geojson"
not_found_file = "washington-not-found.csv"

message = mmqgis_geocode_street_layer(input_csv_name, \
	number_column, street_name_column, zip_column, \
	input_layer, street_name_attr, left_from_attr, left_to_attr, left_zip_attr, \
	right_from_attr, right_to_attr, right_zip_attr, \
	from_x_attr, from_y_attr, to_x_attr, to_y_attr, setback, \
	output_file_name, not_found_file)

print("Geocode Street Layer: " + str(message))

Reverse Geocode

The Reverse Geocode tool uses Google or Nominatim (OpenStreetMap) API to find addresses associated with point feature locations. If features are lines or polygons, the centroid of each feature will be used for querying.

Additional attributes are added to each feature to preserve what the web service geocoded so that accuracy can be assessed. These attributes are dependent upon the geocoder used:

Parameters

Reverse Geocoding Form

Python

mmqgis_geocode_reverse(input_layer, web_service, api_key, use_first,
	output_file_name, status_callback = None)

The following example finds addresses for an input layer of hotel points.

input_file_name = "trump-hotels.geojson"
input_layer = QgsVectorLayer(input_file_name)
web_service = "OpenStreetMap / Nominatim"
api_key = None
use_first = False
output_file_name = "hotel-addresses.geojson"

message = mmqgis_geocode_reverse(input_layer, web_service, api_key, use_first, \
		output_file_name, output_file_format)

print("Reverse geocode OSM: " + str(message))

Import / Export Tools

Attributes Export to CSV File

The Export Attributes tool saves attributes from a map layer to a CSV file, which can then be viewed or edited. This can be helpful when you have some use for the attribute data without the associated geographic data.

This tool is largely duplicates the QGIS native export function (right click on a layer and choose Export). This tool is a legacy of prior QGIS versions that did not have robust export capabilities, and this tool is preserved for folks who like a somewhat simpler user interface.

Parameters

Attribute Export Form

Python

mmqgis_attribute_export(input_layer, attribute_names, output_csv_name, \
			field_delimiter = ",", line_terminator = "\n", decimal_mark = ".", \
			status_callback = None)

The example below exports team name attributes from a list of MLB ballparks.

input_layer_name = "2019-mlb-ballparks.geojson"
input_layer = QgsVectorLayer(input_layer_name)
attribute_names = ["Team", "Location", "Name"]
output_csv_name = "mlb-teams.csv"
field_delimiter = ";"
line_terminator = "\n"
decimal_mark = "."

message = mmqgis_attribute_export(input_layer, attribute_names, output_csv_name, \
		field_delimiter, line_terminator, decimal_mark, status_callback)

print("Export Attributes: " + str(message))

Geometry Export To CSV

The Geometry Export To CSV tool exports points, polylines or polygons to comma-separated variable (CSV) files. Two separate files are created: a node file and an attribute file. This tool is useful for inspecting geometries.

The output node file contains the following columns:

The output attribute file contains all attributes from the source features, plus a shapeid column that associates those attributes with nodes in the node file.

For simple point features, the attributes are included in the node file and no attribute file is created.

Geometries exported using this tool can be re-imported using the Geometry Import from CSV tool.

Note that the two-file CSV format used by this tool is different from the single-file GeoCSV format that stores geometries in a column of Well-Known Text (WKT). GeoCSV files can be created with the native QGIS export function.

Parameters

Geometry Export To CSV Form

Python

mmqgis_geometry_export_to_csv(input_layer, node_file_name, attribute_file_name, \
		field_delimiter = ",", line_terminator = "\n", status_callback = None)

The following example exports a shapefile of US states to CSV geometry files.

layer_file_name = "states-multipart.shp"
input_layer = QgsVectorLayer(layer_file_name)
node_file_name = "states-nodes.csv"
attribute_file_name = "states-attributes.csv"
field_delimiter = ","
line_terminator = "\n"
message = mmqgis_geometry_export_to_csv(input_layer, node_file_name, \
	attribute_file_name, field_delimiter, line_terminator)

print("Export Geometry to CSV: " + str(message))

Geometry Import From CSV

The Geometry Import From CSV tool imports points, polylines, and polygons from comma-separated variable (CSV) files of X/Y nodes.

Other than providing a compliment to the Geometry Export To CSV tool described above, this command can be used to import transit "shapes.txt" data from General Transit Feed Specification (GTFS) releases by transit agencies.

The input CSV file(s) should be encoded in the UTF-8 character set. Although other 8-bit encodings (like Windoze ISO-8859-x) will work if only ASCII characters are present, non-ASCII characters may cause unpredictable behavior.

For point layers, only a single input CSV file is needed that includes latitude and longitude columns. All other columns are imported as attributes. This is essentially the same as the Add Delimited Text Layer native QGIS command. Files are read using the QgsVectorLayer() constructor. Attributes imported as text strings may be converted to floating point using the MMQGIS Text to Float tool.

For polyline and polygon layers, individual nodes of each shape should be provided on separate rows in an input CSV node file. The file should have three columns: Latitude, Longitude, and Shape ID. Any additional columns will be ignored. Nodes for each shape should have the same unique Shape ID value and should occur in the file in the same sequence that they will be added to the shape.

If the imported polylines or polygons have additional attributes to be imported, they should be placed in a separate CSV file with a Shape ID column to join to the imported nodes using the MMQGIS Attributes Join From CSV File command.

Parameters

Geometry Import From CSV Form

Python

mmqgis_geometry_import_from_csv(input_csv_name, shape_id_field, part_id_field, \ 
		geometry_type, latitude_field, longitude_field, \
		output_file_name, status_callback = None)

The following example imports a CSV file of state boundary nodes as multipolygons and then joins attributes to recreate the layer. This is the compliment of the Geometry Export To CSV File example given above.

shape_id_field = "shapeid"
part_id_field = "partid"
geometry_type = "MultiPolygon"
latitude_field = "y"
longitude_field = "x"
output_file_name = "states-nodes.geojson"

message = mmqgis_geometry_import_from_csv(node_file_name, \
		shape_id_field, part_id_field, \
		geometry_type, latitude_field, longitude_field, \
		output_file_name)

print("Import Geometries: " + str(message))

input_layer_name = "states-nodes.geojson"
input_layer = QgsVectorLayer(input_layer_name)
input_layer_attribute = "shapeid"
input_csv_name = "states-attributes.csv"
input_csv_attribute = "shapeid"
output_file_name = "new-states.geojson"

message = mmqgis_attribute_join(input_layer, input_layer_attribute, \
		input_csv_name, input_csv_attribute, output_file_name)

print("Attribute Join: " + str(message))

KML Export

Google Map From Exported KML

The KML Export tool exports features to KML with the capability to explicitly specify fields for the name and description that are always displayed in the Google Maps interface. This tool also styles the KML in an approximation of the symbology configured for the exported layer

Although you can import and export KML in QGIS natively, the OGR/GDAL library used by QGIS does not give strong control over symbology, the name field, or the description field that are displayed in Google Maps.

This tool exports points using the default Google Maps PNG icons. Since a full palette of icon colors is not available, icon colors are approximated based on the RGB values of point layer symbol colors.

Note that Google Maps has a KML size limit of 5 MB. If your layer has highly-detailed polygons, you may need to simplify your geometries using the MMQGIS Gridify tool or the Processing vector geometry Simplify tool.

Parameters

KML Export Form

Python

mmqgis_kml_export(input_layer, name_field, description, export_data,
		  output_file_name, status_callback = None)

The example below creates a red vs blue state KML file using styling defined in the script.

# Gridify (simplify) to keep final file under the Google Maps 5 MB limit
input_layer_name = "2016-electoral-data.geojson"
input_layer = QgsVectorLayer(input_layer_name)
horizontal_spacing = 0.01
vertical_spacing = 0.01
temp_file_name = "temp-states.geojson"

message = mmqgis_gridify_layer(input_layer, horizontal_spacing, \
			vertical_spacing, temp_file_name)
print("Gridify: " + str(message))


# Define the styling for the renderer
red_fill = QgsFillSymbol.createSimple({'color': '#800000', 'outline_color': 'black'})
blue_fill = QgsFillSymbol.createSimple({'color': '#000080', 'outline_color': 'black'})

choropleth_renderer = QgsCategorizedSymbolRenderer("X2016.Winner")
choropleth_renderer.addCategory(QgsRendererCategory("Trump", red_fill, "Trump"))
choropleth_renderer.addCategory(QgsRendererCategory("Clinton", blue_fill, "Clinton"))

# Load the gridified file
input_layer = QgsVectorLayer(temp_file_name)
input_layer.setRenderer(choropleth_renderer)
input_layer.setOpacity(0.5)

# Export to KML
name_field = "State.Name"
description = "<p>State Name: {{State.Name}}" + \
	"<br/>2016 Winner: {{X2016.Winner}}" + \
	"<br/>Democratic Percent: {{X2016.Democratic.Percent}}" + \
	"<br/>Republican Percent: {{X2016.Republican.Percent}}</p>"
export_data = False
output_file_name = "red-blue-states.kml"

message = mmqgis_kml_export(input_layer, name_field, description, export_data, output_file_name, status_callback)
print("KML Export: " + str(message))

The code can be simplified if you get your styling from a QGIS project file.

# This must be a full path name (not relative) or the layers will not load
project_file = "/home/michael/software/mmqgis/tests/kml-export/kml-export.qgs"
project = QgsProject.instance()
project.read(project_file)
input_layer = project.mapLayersByName("2016-electoral-data-gridified")[0]

name_field = "State.Name"
description = "<p>State Name: {{State.Name}}" + \
	"<br/>2016 Winner: {{X2016.Winner}}" + \
	"<br/>Democratic Percent: {{X2016.Democratic.Percent}}" + \
	"<br/>Republican Percent: {{X2016.Republican.Percent}}</p>"
export_data = False
output_file_name = "red-blue-states2.kml"

message = mmqgis_kml_export(input_layer, name_field, description, \
		export_data, output_file_name)

print("KML Export From a Project File: " + str(message))

Modify Tools

Change Projection

The Change Projection tool changes the projection (reprojects) a layer to a new projection and transforms the geometries of all features in that layer to the new projection.

The dialog displays the PROJ string for the existing and new projections.

This tool largely duplicates the capabilities of the Reproject layer tool from the Processing toolkit.

Parameters

Change Projection Form

Python

mmqgis_change_projection(input_layer, new_crs, output_file_name, status_callback = None)

The example below changes the projection of a USCB cartographic boundary file for the US to a North American Albers projection:

input_layer_name = "cb_2018_us_state_20m.shp"
input_layer = QgsVectorLayer(input_layer_name)

new_crs = QgsCoordinateReferenceSystem("PROJ4:+proj=aea +lat_1=20 +lat_2=60 " + \
	"+lat_0=40 +lon_0=-96 +x_0=0 +y_0=0 +datum=NAD83 +units=m +no_defs")
output_file_name = "usa-albers.geojson"

message = mmqgis_change_projection(input_layer, new_crs, output_file_name)
print("Change projection: " + str(message))

Convert Geometry

The Geometry Convert tool changes the geometry types of shapes.

Parameters

Geometry Convert Form

Python

mmqgis_geometry_convert(input_layer, new_geometry, output_file_name, status_callback = None)

The following breaks a multipart state file (including islands) into single parts.

input_layer_name = "states-multipart.shp"
input_layer = QgsVectorLayer(input_layer_name)
new_geometry = "Polygons"
output_file_name = "state-polygons.geojson"
message = mmqgis_geometry_convert(input_layer, new_geometry, output_file_name)
print("Multipolygons to Polygons: " + str(message))

Delete Duplicate Geometries

The Delete Duplicate Geometries tool removes duplicate features from a layer. Two features are considered duplicates if their geometries are exactly identical, as determined by the QgsGeometry equals() function. Attributes are not considered, only geometry.

To improve speed, this tool loads all geometries into memory - which may cause memory issues with large datasets or large numbers of complex polygons.

Parameters

Delete Duplicate Geometries Form

Python

mmqgis_delete_duplicate_geometries(input_layer, output_file_name, status_callback = None)

This example removes duplicates from a layer.

	layer_file = "duplicates.shp"
	input_layer = QgsVectorLayer(layer_file)
	output_file_name = "no-duplicates.geojson"

	message = mmqgis_delete_duplicate_geometries(input_layer, output_file_name)

	print("Delete duplicate geometries: " + str(message))

Float to Text

The Float to Text tool converts integer and floating point values to strings, with the ability to specify thousands separators, the number of decimal digits, prefixes and suffixes (eg. dollar sign prefix or "miles" suffix). This is the opposite of the Text to Float tool.

Parameters

Float to Text Form

Python

 mmqgis_float_to_text(input_layer, attributes, separator, \
			 decimals, prefix, suffix, multiplier, \
		 	 output_file_name, status_callback = None)

The following converts land and water areas from square meters to square miles from a US Census Bureau state cartographic boundary file.

input_layer_name = "cb_2018_us_state_20m.shp"
input_layer = QgsVectorLayer(layer_file)
attributes = ["ALAND", "AWATER"]
separator = ","
decimals = 2
multiplier = 0.000000386102
prefix = None
suffix = " sq mi"
output_file_name = "state-area.geojson"

message = mmqgis_float_to_text(input_layer, attributes, separator, \
	decimals, prefix, suffix, output_file_name)

print("Float to Text: " + str(message))

Gridify

Example of Gridification

The Gridify tool simplifies points, lines and polygons in a shapefile by aligning all vertices to a specified grid and then removing redundant points.

Gridification makes it possible to significantly improve display refresh time when working with shapefiles that are more detailed than is necessary for the final map. However, the gridification process can result in some odd artifacts when viewed at higher resolutions, particularly in dealing with coves and other appendages along the edges of larger polygons.

Parameters

Gridify Form

Python

mmqgis_gridify_layer(input_layer, horizontal_spacing, vertical_spacing,
		     output_file_name, status_callback = None)

The following example gridifies a file of US states to reduce the size so it can be exported to KML.

# Gridify (simplify) to keep final file under the Google Maps 5 MB limit
input_layer_name = "2016-electoral-data.geojson"
input_layer = QgsVectorLayer(input_layer_name)
horizontal_spacing = 0.01
vertical_spacing = 0.01
temp_file_name = "temp-states.geojson"

message = mmqgis_gridify_layer(input_layer, horizontal_spacing, \
			vertical_spacing, temp_file_name)
print("Gridify: " + str(message))

Sort

The Sort tool sorts features in the data. Although the QGIS Open Attribute Table feature can sort displayed attributes dynamically by any field, the Sort tool can make exported attributes easier to browse and analyze.

Parameters

Python

mmqgis_sort(input_layer, sort_field, sort_direction, output_file_name, status_callback = None)

The following example sorts a file of states by name.

input_layer_name = "statesp020.shp"
input_layer = QgsVectorLayer(input_layer_name)
sort_field = "STATE"
sort_direction = "Ascending"
output_file_name = "states-sorted.geojson"

message = mmqgis_sort(input_layer, sort_field, sort_direction, output_file_name)

print("Ascending sort: " + str(message))

Text to Float

The Text to Float tool converts string attributes to floating point. This tool is useful for imported data where numeric fields are stored as strings and contain extraneous non-numeric characters like currency symbols, thousands separators, and/or footnote marks.

Parameters

Text to Float Form

Python

mmqgis_text_to_float(input_layer, field_names, output_file_name, status_callback = None)

The following example converts the land and water area fields back to numeric fields. This is the opposite of the Float to Text example above.

input_layer_name = "state-area.geojson"
input_layer = QgsVectorLayer(layer_file)
field_names = ["ALAND", "AWATER"]
output_file_name = "state-float.geojson"

message = mmqgis_text_to_float(input_layer, field_names, output_file_name)

print("Text to Float: " + str(message))

Search / Select

Search Docking Widget

The Search / Select widget permits interactive browsing of features that match the specified search criteria. Results are displayed in a list box in the search widget.

Clicking one of the features on the results box moves the map canvas to center that feature in the window.

This tool also provides options in the layer selection combo box for searching addresses using Nominatim / Open Street Map. Selection of an address only pans the map display to place the location in the center of the display, and no markings are added to the map.

Multiple selections in the results list are possible using the CTRL and SHIFT keys. If multiple features are selected, the map canvas centers on a central area between the centroids of all selected features.

Layer feature searches can be performed on one or two specific attribute fields. If a value is specified for a second attribute, a feature must match BOTH conditions to be included in the results list.

Layer feature searches can also be made across all layers by choosing the [All Layers] in the layers combo box, and/or all fields in the specified layers by choosing the [all] attributes option in the attribute combo box. Choosing to search all layers or attributes can be time-consuming, and/or yield an unmanageable set of search result features.

Possible comparisons on numeric or string attributes are the common permutations of equal, greater than, or less than. String attributes can be searched with "contains" or "begins with." Searches are case insensitive.

Python

The search widget is an interactive tool that is integrated with the QGIS user interface and does not have a library function. You can perform searches on layers with expressions using methods like QgsVectorLayer.getFeatures().

Programming with MMQGIS

Console Programming

MMQGIS tools are built around Python functions that can be used from the QGIS Python console by importing the mmqgis_library.py functions from the plugin directory using the import command. Output feature files can then be added to the current map project using the iface.addVectorLayer() function.

The following example uses the mmqgis_grid() code snippet from above to create and add a 10-degree graticule layer to the current project:

from mmqgis.mmqgis_library import *

geometry_type = "Lines"
crs = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")
x_spacing = 10
y_spacing = 10
x_left = -170
x_right = 170
y_bottom = -80
y_top = 80
output_file_name = "world-grid.geojson"

mmqgis_grid(geometry_type, crs, x_spacing, y_spacing, x_left, y_bottom, x_right, y_top, output_file_name)

iface.addVectorLayer(output_file_name, "Graticule", "ogr")

QGIS Python Console Scripts

Your code can also be placed in external script files and executed from the QGIS Python console.

For example, if you create a script such as the one above and place it in a file, you can execute it in the QGIS python console in the same way you would execute scripts from the Python interpreter shell:

exec(open("/home/michael/test.py").read())

Note that you should only use exit() commands in your script if you want to exit QGIS as well.

Stand-Alone Programming

QGIS library functions and MMQGIS tools can also be used in stand-alone Python scripts that you can run without having to start QGIS. You need to include the following code at the beginning of your script to load the QGIS libraries and create a QGIS instance:

from qgis.core import *
qgs = QgsApplication([], True)
qgs.initQgis()

As with console scripts, you need to import the mmqgis_library module. However, you may need to add the directory containing mmqgis_library.py to your sys.path.

The example below is a version of the grid script given above that can be executed from a stand-alone Python script. The folder added to sys.path will differ depending on where MMQGIS is installed on your system.

from qgis.core import *
qgis = QgsApplication([], True)
qgis.initQgis()

import sys
sys.path.append("/home/michael/software/mmqgis/")
from mmqgis_library import *

geometry_type = "Lines"
crs = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")
x_spacing = 10
y_spacing = 10
x_left = -170
x_right = 170
y_bottom = -80
y_top = 80
output_file_name = "world-grid.geojson"

mmqgis_grid(geometry_type, crs, x_spacing, y_spacing, x_left, y_bottom, x_right, y_top, output_file_name)

Multiple File Operations

One of the major strengths of scripting is the ability to perform the same operation on multiple data files.

You can explicitly specify multiple files in an array. For example, this script changes the projection on a set of files to WGS 84 latitude/longitude.

from qgis.core import *
qgis = QgsApplication([], True)
qgis.initQgis()

from mmqgis_library import *

input_file_names = [ "alpha.shp", "beta.shp", "gamma.shp" ]

wgs84 = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")

for input_file_name in input_file_names:
	input_layer = QgsVectorLayer(input_file_name)
	output_file_name = input_file_name.replace(".shp", "-reprojected.shp")
	message = mmqgis_change_projection(input_layer, wgs84, output_file_name)
	print(input_file_name + " = " + output_file_name + " = " + str(message))

You can also use the Python scandir() function to get a list of files in a directory.

from qgis.core import *
qgis = QgsApplication([], True)
qgis.initQgis()

from mmqgis_library import *

import os
os.chdir("tests/batch")

input_file_names = []
for input_file_name in os.scandir("."):
	if input_file_name.name[-4:] == ".shp":
		input_file_names.append(input_file_name.name)

wgs84 = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")

for input_file_name in input_file_names:
	input_layer = QgsVectorLayer(input_file_name)
	output_file_name = input_file_name.replace(".shp", "-reprojected.shp")
	message = mmqgis_change_projection(input_layer, wgs84, output_file_name)
	print(input_file_name + " = " + output_file_name + " = " + str(message))

Status Callback

All MMQGIS library functions have an optional status_callback parameter that permits display of status messages as the tool completes and provides a way of terminating tools when they exceed a time limit. The callback function had two parameters: the percent completed and a string status message.

This example defines a simple status_callback that prints status messages to the screen.

from qgis.core import *
qgis = QgsApplication([], True)
qgis.initQgis()

from mmqgis_library import *

import os
os.chdir("tests/batch")

def status_callback(percent, message):
	print("\t" + str(int(percent)) + "%: " + message)
	return 0

input_file_names = []
for input_file_name in os.scandir("."):
	if os.path.splitext(input_file_name.name)[1] == ".shp":
		input_file_names.append(input_file_name.name)

wgs84 = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")

for input_file_name in input_file_names:
	input_layer = QgsVectorLayer(input_file_name)
	output_file_name = input_file_name.replace(".shp", "-reprojected.shp")
	message = mmqgis_change_projection(input_layer, wgs84, output_file_name)
	print(input_file_name + " = " + output_file_name + " = " + str(message))

Image File Output

Layers can be exported as maps to PNG files. The easiest course of action is to set up the print layout in the layout editor in QGIS and then use that layout from the project file in your script.

These examples use election data from the AP via Politico: 2016-electoral-data.geojson

Exported Map Image
from qgis.core import *
qgis = QgsApplication([], True)
qgis.initQgis()

# Load the project
# This needs to be a full path, otherwise layers do not get read in
project_file = "/home/michael/software/mmqgis/tests/kml-export/2016-election-osm-map.qgs"
project = QgsProject.instance()
project.read(project_file)
if project.error():
	exit("Error: " + project.error())

# Get the print layout from the project
print_layout = QgsProject.instance().layoutManager().printLayouts()[0]

# Create an exporter and export the layout to a PNG file
exporter = QgsLayoutExporter(print_layout)
exporter.exportToImage("2016-election-osm-map.png", exporter.ImageExportSettings())

However, if you are batch exporting images from a variety of different layers, you need programmatic control of your layout, or you just want to setup a print layout that you can duplicate and modify without having to use the QGIS interface, you can define print layouts in Python, although these are cumbersome for complex symbology and/or layouts.

# Create the OpenStreetMap tiled base map
osm_url = 'type=xyz&url=https://a.tile.openstreetmap.org/' +\
	'%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0&crs=EPSG3857'
osm_layer = QgsRasterLayer(osm_url, 'OpenStreetMap', 'wms')

# Define the extent of the display window
wgs84 = QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs")
transform = QgsCoordinateTransform(wgs84, osm_layer.crs(), QgsProject().instance())

usa_extent = QgsRectangle(-129, 24, -59, 52)
osm_extent = transform.transform(usa_extent)

# Define the renderer for the choropleth
red_fill = QgsFillSymbol.createSimple({'color': '#800000', 'outline_color': 'black'})
blue_fill = QgsFillSymbol.createSimple({'color': '#000080', 'outline_color': 'black'})

choropleth_renderer = QgsCategorizedSymbolRenderer("X2016.Winner")
choropleth_renderer.addCategory(QgsRendererCategory("Trump", red_fill, "Trump"))
choropleth_renderer.addCategory(QgsRendererCategory("Clinton", blue_fill, "Clinton"))

choropleth_layer = QgsVectorLayer("2016-electoral-data.geojson")
choropleth_layer.setRenderer(choropleth_renderer)

# Create the print layout with a single page for a 1280x720 image at 300 DPI
print_layout = QgsPrintLayout(QgsProject.instance())
print_layout.initializeDefaults()
page_size = QgsLayoutSize(108.4, 61, QgsUnitTypes.LayoutMillimeters);
print_layout.pageCollection().page(0).setPageSize(page_size)

map_item = QgsLayoutItemMap(print_layout)
map_item.setFixedSize(page_size)
map_item.attemptMove(QgsLayoutPoint(0, 0))
map_item.setCrs(osm_layer.crs())
map_item.setExtent(usa_extent)
map_item.setLayers([ choropleth_layer, osm_layer ])

print_layout.addLayoutItem(map_item)

# Create an exporter and export the layout to a PNG file
exporter = QgsLayoutExporter(print_layout)
exporter.exportToImage("2016-election-osm-map.png", exporter.ImageExportSettings())

Map Window Display

Maps can also be displayed to map canvas windows created in Python. However, as with the image output examples above, you will need to set up renderers for your layers if you want anything beyond the default styling.

Note the use of the qgis object created by the QgsApplication() initializer (see above) and the addition of from qgis.gui import QgsMapCanvas to use a QGIS map canvas.

from qgis.core import *
qgis = QgsApplication([], True)
qgis.initQgis()

# Create the OpenStreetMap tiled base map
osm_url = 'type=xyz&url=https://a.tile.openstreetmap.org/%7Bz%7D/%7Bx%7D/%7By%7D.png&zmax=19&zmin=0&crs=EPSG3857'
osm_layer = QgsRasterLayer(osm_url, 'OpenStreetMap', 'wms')

# Define the extent of the display window
transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem("PROJ4:+proj=longlat +datum=WGS84 +no_defs"),
	osm_layer.crs(), QgsProject().instance())

usa_extent = QgsRectangle(-129, 24, -59, 52)
osm_extent = transform.transform(usa_extent)

# Define the renderer for the choropleth
red_fill = QgsFillSymbol.createSimple({'color': '#800000', 'outline_color': 'black'})
blue_fill = QgsFillSymbol.createSimple({'color': '#000080', 'outline_color': 'black'})

choropleth_renderer = QgsCategorizedSymbolRenderer("X2016.Winner")
choropleth_renderer.addCategory(QgsRendererCategory("Trump", red_fill, "Trump"))
choropleth_renderer.addCategory(QgsRendererCategory("Clinton", blue_fill, "Clinton"))

choropleth_layer = QgsVectorLayer("2016-electoral-data.geojson")
choropleth_layer.setRenderer(choropleth_renderer)


# Create a map canvas and display the layers
from qgis.gui import QgsMapCanvas

canvas = QgsMapCanvas()
canvas.setExtent(osm_extent)
canvas.setDestinationCrs(osm_layer.crs())
canvas.setLayers([ choropleth_layer, osm_layer ])
canvas.show()
qgis.exec_()

Version History

v2019.08.11

v2019.06.11

v2018.08.09

v2018.07.10

v2018.02.27

v2018.01.02

v2017.05.14

v2017.02.28

v2017.02.12

v2016.12.29

v2016.10.26

v2016.10.19

v2016.10.18

v2016.10.12

v2016.08.18

v2016.08.17

v2016.08.04

v2016.05.15

v2016.01.31

v2015.11.03

v2015.10.12

v2015.08.08

v2015.05.27

v2015.02.11

v2015.01.29

v2014.09.19

v2014.08.25

v2014.08.24

v2014.08.03

v2014.07.25

v2014.07.10

v2014.06.08

v2014.02.28

v2014.02.24

v2014.01.06

v2014.01.01

v2013.07.16

v2013.07.15

v2013.06.14

v2013.03.23

v2013.03.14

v2012.12.07

v2012.10.25

v2012.10.19

v2012.08.28

v2012.05.10

v2012.05.06

v2012.04.06

v2012.01.11

v2011.11.12

v2011.10.08

v2011.10.07

v2011.10.06

v2011.09.14

v2011.08.27

v2011.08.22

v2011.08.16

v2011.07.30

v2011.03.14

v2011.01.21

v2011.01.08

v2010.09.29

v2010.01.02

v2009.09.04

v2009.09.01

v2009.08.28