[{"data":1,"prerenderedAt":1966},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reproject-vector-layers-pyqgis":3},{"id":4,"title":5,"body":6,"description":1955,"extension":1956,"meta":1957,"navigation":259,"path":1962,"seo":1963,"stem":1964,"__hash__":1965},"docs\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reproject-vector-layers-pyqgis\u002Findex.md","Batch Reproject Vector Layers in PyQGIS",{"type":7,"value":8,"toc":1941},"minimark",[9,13,17,31,167,172,193,197,200,594,610,614,617,676,887,900,904,907,1229,1242,1246,1249,1390,1407,1411,1414,1490,1631,1636,1640,1643,1802,1807,1811,1825,1829,1855,1859,1862,1866,1872,1878,1892,1898,1904,1908,1937],[10,11,5],"h1",{"id":12},"batch-reproject-vector-layers-in-pyqgis",[14,15,16],"p",{},"Data collected over years arrives in a patchwork of coordinate systems: a national grid from the mapping agency, UTM from a GPS survey, WGS 84 from a web export, a legacy local system from an old CAD drawing. QGIS reprojects on the fly for display, but analysis, exchange and storage are far simpler when everything shares one CRS. Reprojecting dozens or hundreds of layers by hand is slow and error-prone; a script does it consistently — with the same datum transformation every time, which matters more than most people expect.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","Coordinate Reference Systems",". It inventories layers and their CRSs, reprojects them with an explicit transformation, writes the results into one GeoPackage while keeping names and styles, skips what does not need work, verifies positions, and logs every step. Rasters are covered separately in ",[21,27,29],{"href":28},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reprojecting-raster-datasets\u002F","batch reprojecting raster datasets",".",[14,32,33],{},[34,35,40,44,48,55,72,81,89,95,100,104,111,116,120,123,126,130,136,140,143,146,150,156,161,164],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 226","img","A batch reprojection pipeline: inventory CRSs, skip or reproject with an explicit transformation, write to one GeoPackage, verify and log","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Inventory, transform, write, verify",[45,46,47],"desc",{},"Each source layer is inventoried for its CRS. Layers already in the target CRS are copied unchanged. Others are reprojected with native:reprojectlayer using one explicitly chosen datum transformation, written as tables in a single target GeoPackage with their original names and styles. A check point with known coordinates in both systems verifies the result, and a log records every layer.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","226","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"brFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","One target CRS, one transformation, one log",[49,82],{"x":83,"y":84,"width":85,"height":85,"rx":63,"fill":86,"stroke":87,"style":88},"24","60","150","#fffdf7","#59645f","stroke-width:2",[73,90,94],{"x":91,"y":92,"style":93,"fill":78,"textAnchor":79},"99","112.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","sources",[73,96,99],{"x":91,"y":97,"style":98,"fill":87,"textAnchor":79},"138.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","shp, gpkg,",[73,101,103],{"x":91,"y":102,"style":98,"fill":87,"textAnchor":79},"164.78","geojson",[105,106],"line",{"x1":107,"y1":108,"x2":109,"y2":108,"stroke":71,"style":110},"174","135","210","stroke-width:1.8;marker-end:url(#brFlowArrow)",[49,112],{"x":113,"y":84,"width":85,"height":85,"rx":63,"fill":114,"stroke":115,"style":88},"214","#fdf2e2","#b45309",[73,117,119],{"x":118,"y":92,"style":93,"fill":115,"textAnchor":79},"289","inventory",[73,121,122],{"x":118,"y":97,"style":98,"fill":87,"textAnchor":79},"CRS per layer",[73,124,125],{"x":118,"y":102,"style":98,"fill":87,"textAnchor":79},"skip if target",[105,127],{"x1":128,"y1":108,"x2":129,"y2":108,"stroke":71,"style":110},"364","400",[49,131],{"x":132,"y":84,"width":133,"height":85,"rx":63,"fill":134,"stroke":135,"style":88},"404","160","#eef7f4","#0f766e",[73,137,139],{"x":138,"y":92,"style":93,"fill":135,"textAnchor":79},"484","reproject",[73,141,142],{"x":138,"y":97,"style":98,"fill":87,"textAnchor":79},"explicit",[73,144,145],{"x":138,"y":102,"style":98,"fill":87,"textAnchor":79},"transformation",[105,147],{"x1":148,"y1":108,"x2":149,"y2":108,"stroke":71,"style":110},"564","600",[49,151],{"x":152,"y":84,"width":153,"height":85,"rx":63,"fill":154,"stroke":155,"style":88},"604","132","#e8efe6","#15803d",[73,157,160],{"x":158,"y":92,"style":93,"fill":159,"textAnchor":79},"670","#166534","GeoPackage",[73,162,163],{"x":158,"y":97,"style":98,"fill":87,"textAnchor":79},"names, styles",[73,165,166],{"x":158,"y":102,"style":98,"fill":87,"textAnchor":79},"+ log",[168,169,171],"h2",{"id":170},"prerequisites","Prerequisites",[173,174,175,179,186],"ul",{},[176,177,178],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[176,180,181,182,30],{},"A target CRS chosen deliberately — see ",[21,183,185],{"href":184},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fchoose-projected-crs-for-analysis-pyqgis\u002F","choosing a projected CRS for analysis",[176,187,188,189,30],{},"Correct source CRSs. Reprojecting a layer whose CRS is wrongly labelled moves it to the wrong place with great precision; check first, as in ",[21,190,192],{"href":191},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fassign-crs-without-reprojecting-pyqgis\u002F","assigning a CRS without reprojecting",[168,194,196],{"id":195},"inventory-the-sources","Inventory the sources",[14,198,199],{},"Before transforming anything, list what is there and in which CRS. The inventory decides what needs work and exposes layers with missing or suspicious CRS definitions.",[201,202,207],"pre",{"className":203,"code":204,"language":205,"meta":206,"style":206},"language-python shiki shiki-themes github-dark","from pathlib import Path\nfrom collections import Counter\nfrom qgis.core import QgsVectorLayer, QgsProviderRegistry\n\ndef vector_sources(folder):\n    for path in sorted(Path(folder).rglob(\"*\")):\n        if path.suffix.lower() not in {\".shp\", \".gpkg\", \".geojson\", \".fgb\", \".gml\"}:\n            continue\n        md = QgsProviderRegistry.instance().providerMetadata(\"ogr\")\n        for sub in md.querySublayers(str(path)) if path.suffix.lower() == \".gpkg\" else [None]:\n            uri = sub.uri() if sub else str(path)\n            name = sub.name() if sub else path.stem\n            layer = QgsVectorLayer(uri, name, \"ogr\")\n            if layer.isValid() and layer.isSpatial():\n                yield name, uri, layer\n\ninventory = [(name, uri, lyr.crs()) for name, uri, lyr in vector_sources(\"\u002Fdata\u002Finbox\")]\nprint(Counter(crs.authid() or \"(no authid)\" for _, _, crs in inventory).most_common())\nfor name, _, crs in inventory:\n    if not crs.isValid():\n        print(\"NO CRS:\", name)\n","python","",[208,209,210,228,241,254,261,274,300,345,351,369,412,436,456,471,486,495,500,528,554,567,579],"code",{"__ignoreMap":206},[211,212,214,218,222,225],"span",{"class":105,"line":213},1,[211,215,217],{"class":216},"snl16","from",[211,219,221],{"class":220},"s95oV"," pathlib ",[211,223,224],{"class":216},"import",[211,226,227],{"class":220}," Path\n",[211,229,231,233,236,238],{"class":105,"line":230},2,[211,232,217],{"class":216},[211,234,235],{"class":220}," collections ",[211,237,224],{"class":216},[211,239,240],{"class":220}," Counter\n",[211,242,244,246,249,251],{"class":105,"line":243},3,[211,245,217],{"class":216},[211,247,248],{"class":220}," qgis.core ",[211,250,224],{"class":216},[211,252,253],{"class":220}," QgsVectorLayer, QgsProviderRegistry\n",[211,255,257],{"class":105,"line":256},4,[211,258,260],{"emptyLinePlaceholder":259},true,"\n",[211,262,264,267,271],{"class":105,"line":263},5,[211,265,266],{"class":216},"def",[211,268,270],{"class":269},"svObZ"," vector_sources",[211,272,273],{"class":220},"(folder):\n",[211,275,277,280,283,286,290,293,297],{"class":105,"line":276},6,[211,278,279],{"class":216},"    for",[211,281,282],{"class":220}," path ",[211,284,285],{"class":216},"in",[211,287,289],{"class":288},"sDLfK"," sorted",[211,291,292],{"class":220},"(Path(folder).rglob(",[211,294,296],{"class":295},"sU2Wk","\"*\"",[211,298,299],{"class":220},")):\n",[211,301,303,306,309,312,315,318,321,324,327,329,332,334,337,339,342],{"class":105,"line":302},7,[211,304,305],{"class":216},"        if",[211,307,308],{"class":220}," path.suffix.lower() ",[211,310,311],{"class":216},"not",[211,313,314],{"class":216}," in",[211,316,317],{"class":220}," {",[211,319,320],{"class":295},"\".shp\"",[211,322,323],{"class":220},", ",[211,325,326],{"class":295},"\".gpkg\"",[211,328,323],{"class":220},[211,330,331],{"class":295},"\".geojson\"",[211,333,323],{"class":220},[211,335,336],{"class":295},"\".fgb\"",[211,338,323],{"class":220},[211,340,341],{"class":295},"\".gml\"",[211,343,344],{"class":220},"}:\n",[211,346,348],{"class":105,"line":347},8,[211,349,350],{"class":216},"            continue\n",[211,352,354,357,360,363,366],{"class":105,"line":353},9,[211,355,356],{"class":220},"        md ",[211,358,359],{"class":216},"=",[211,361,362],{"class":220}," QgsProviderRegistry.instance().providerMetadata(",[211,364,365],{"class":295},"\"ogr\"",[211,367,368],{"class":220},")\n",[211,370,372,375,378,380,383,386,389,392,394,397,400,403,406,409],{"class":105,"line":371},10,[211,373,374],{"class":216},"        for",[211,376,377],{"class":220}," sub ",[211,379,285],{"class":216},[211,381,382],{"class":220}," md.querySublayers(",[211,384,385],{"class":288},"str",[211,387,388],{"class":220},"(path)) ",[211,390,391],{"class":216},"if",[211,393,308],{"class":220},[211,395,396],{"class":216},"==",[211,398,399],{"class":295}," \".gpkg\"",[211,401,402],{"class":216}," else",[211,404,405],{"class":220}," [",[211,407,408],{"class":288},"None",[211,410,411],{"class":220},"]:\n",[211,413,415,418,420,423,425,427,430,433],{"class":105,"line":414},11,[211,416,417],{"class":220},"            uri ",[211,419,359],{"class":216},[211,421,422],{"class":220}," sub.uri() ",[211,424,391],{"class":216},[211,426,377],{"class":220},[211,428,429],{"class":216},"else",[211,431,432],{"class":288}," str",[211,434,435],{"class":220},"(path)\n",[211,437,439,442,444,447,449,451,453],{"class":105,"line":438},12,[211,440,441],{"class":220},"            name ",[211,443,359],{"class":216},[211,445,446],{"class":220}," sub.name() ",[211,448,391],{"class":216},[211,450,377],{"class":220},[211,452,429],{"class":216},[211,454,455],{"class":220}," path.stem\n",[211,457,459,462,464,467,469],{"class":105,"line":458},13,[211,460,461],{"class":220},"            layer ",[211,463,359],{"class":216},[211,465,466],{"class":220}," QgsVectorLayer(uri, name, ",[211,468,365],{"class":295},[211,470,368],{"class":220},[211,472,474,477,480,483],{"class":105,"line":473},14,[211,475,476],{"class":216},"            if",[211,478,479],{"class":220}," layer.isValid() ",[211,481,482],{"class":216},"and",[211,484,485],{"class":220}," layer.isSpatial():\n",[211,487,489,492],{"class":105,"line":488},15,[211,490,491],{"class":216},"                yield",[211,493,494],{"class":220}," name, uri, layer\n",[211,496,498],{"class":105,"line":497},16,[211,499,260],{"emptyLinePlaceholder":259},[211,501,503,506,508,511,514,517,519,522,525],{"class":105,"line":502},17,[211,504,505],{"class":220},"inventory ",[211,507,359],{"class":216},[211,509,510],{"class":220}," [(name, uri, lyr.crs()) ",[211,512,513],{"class":216},"for",[211,515,516],{"class":220}," name, uri, lyr ",[211,518,285],{"class":216},[211,520,521],{"class":220}," vector_sources(",[211,523,524],{"class":295},"\"\u002Fdata\u002Finbox\"",[211,526,527],{"class":220},")]\n",[211,529,531,534,537,540,543,546,549,551],{"class":105,"line":530},18,[211,532,533],{"class":288},"print",[211,535,536],{"class":220},"(Counter(crs.authid() ",[211,538,539],{"class":216},"or",[211,541,542],{"class":295}," \"(no authid)\"",[211,544,545],{"class":216}," for",[211,547,548],{"class":220}," _, _, crs ",[211,550,285],{"class":216},[211,552,553],{"class":220}," inventory).most_common())\n",[211,555,557,559,562,564],{"class":105,"line":556},19,[211,558,513],{"class":216},[211,560,561],{"class":220}," name, _, crs ",[211,563,285],{"class":216},[211,565,566],{"class":220}," inventory:\n",[211,568,570,573,576],{"class":105,"line":569},20,[211,571,572],{"class":216},"    if",[211,574,575],{"class":216}," not",[211,577,578],{"class":220}," crs.isValid():\n",[211,580,582,585,588,591],{"class":105,"line":581},21,[211,583,584],{"class":288},"        print",[211,586,587],{"class":220},"(",[211,589,590],{"class":295},"\"NO CRS:\"",[211,592,593],{"class":220},", name)\n",[14,595,596,600,601,604,605,609],{},[597,598,599],"strong",{},"Breakdown:"," ",[208,602,603],{},"querySublayers"," lists the tables inside each GeoPackage so every layer is found, not just the first; plain files are treated as one layer each. Counting CRSs by authority id shows the spread at a glance — usually a handful of systems. A layer with no valid CRS cannot be reprojected safely; resolve it before continuing, as described in ",[21,606,608],{"href":607},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fhandling-missing-crs-in-pyqgis\u002F","handling missing CRS",". A layer with a custom CRS and no authority id is worth a look too: it is often a known system with a slightly different definition.",[168,611,613],{"id":612},"choose-the-transformation-explicitly","Choose the transformation explicitly",[14,615,616],{},"Between two CRSs on different datums there can be several transformations with very different accuracy — from a few metres to a few centimetres. Letting each machine pick its own default makes results depend on which grids happen to be installed.",[14,618,619],{},[34,620,623,626,629,632,635,639,643,647,651,654,657,660,663,666,670,673],{"viewBox":621,"role":37,"ariaLabel":622,"xmlns":39},"0 0 760 222","Alternative datum transformations between two CRSs with different accuracies, from metres for simple shifts to centimetres for grid-based methods",[41,624,625],{},"Several ways from A to B",[45,627,628],{},"From a national grid on an old datum to ETRS89 UTM there are typically several transformations: a three-parameter Helmert shift accurate to a few metres, a seven-parameter Helmert accurate to about a metre, and a grid-based transformation accurate to centimetres if the grid file is installed. A batch should pick one, record it, and use it for every layer.",[49,630],{"x":51,"y":51,"width":52,"height":631,"fill":54},"222",[73,633,634],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Pick one, record it, use it everywhere",[49,636],{"x":83,"y":637,"width":631,"height":85,"rx":63,"fill":114,"stroke":638,"style":88},"56","#b91c1c",[73,640,642],{"x":108,"y":641,"style":93,"fill":638,"textAnchor":79},"106.78","3-param Helmert",[73,644,646],{"x":108,"y":645,"style":98,"fill":87,"textAnchor":79},"134.78","2–5 m",[73,648,650],{"x":108,"y":649,"style":98,"fill":87,"textAnchor":79},"162.78","always available",[49,652],{"x":653,"y":637,"width":631,"height":85,"rx":63,"fill":114,"stroke":115,"style":88},"269",[73,655,656],{"x":75,"y":641,"style":93,"fill":115,"textAnchor":79},"7-param Helmert",[73,658,659],{"x":75,"y":645,"style":98,"fill":87,"textAnchor":79},"≈ 1 m",[73,661,662],{"x":75,"y":649,"style":98,"fill":87,"textAnchor":79},"usually available",[49,664],{"x":665,"y":637,"width":631,"height":85,"rx":63,"fill":154,"stroke":155,"style":88},"514",[73,667,669],{"x":668,"y":641,"style":93,"fill":159,"textAnchor":79},"625","grid-based",[73,671,672],{"x":668,"y":645,"style":98,"fill":87,"textAnchor":79},"cm level",[73,674,675],{"x":668,"y":649,"style":98,"fill":87,"textAnchor":79},"needs grid file",[201,677,679],{"className":203,"code":678,"language":205,"meta":206,"style":206},"from qgis.core import (QgsCoordinateReferenceSystem, QgsDatumTransform,\n                       QgsCoordinateTransformContext, QgsProject)\n\ntarget = QgsCoordinateReferenceSystem(\"EPSG:25832\")\nsource = QgsCoordinateReferenceSystem(\"EPSG:31467\")           # DHDN \u002F Gauss-Krüger zone 3\n\nops = QgsDatumTransform.operations(source, target)\nfor i, op in enumerate(ops):\n    print(i, f\"±{op.accuracy} m\" if op.accuracy >= 0 else \"accuracy unknown\",\n          \"available\" if op.isAvailable else \"MISSING GRID\", op.name)\n\nbest = next(op for op in ops if op.isAvailable)\ncontext = QgsCoordinateTransformContext()\ncontext.addCoordinateOperation(source, target, best.proj)\nprint(\"using:\", best.name)\n",[208,680,681,692,697,701,716,735,739,749,764,810,828,832,860,870,875],{"__ignoreMap":206},[211,682,683,685,687,689],{"class":105,"line":213},[211,684,217],{"class":216},[211,686,248],{"class":220},[211,688,224],{"class":216},[211,690,691],{"class":220}," (QgsCoordinateReferenceSystem, QgsDatumTransform,\n",[211,693,694],{"class":105,"line":230},[211,695,696],{"class":220},"                       QgsCoordinateTransformContext, QgsProject)\n",[211,698,699],{"class":105,"line":243},[211,700,260],{"emptyLinePlaceholder":259},[211,702,703,706,708,711,714],{"class":105,"line":256},[211,704,705],{"class":220},"target ",[211,707,359],{"class":216},[211,709,710],{"class":220}," QgsCoordinateReferenceSystem(",[211,712,713],{"class":295},"\"EPSG:25832\"",[211,715,368],{"class":220},[211,717,718,721,723,725,728,731],{"class":105,"line":263},[211,719,720],{"class":220},"source ",[211,722,359],{"class":216},[211,724,710],{"class":220},[211,726,727],{"class":295},"\"EPSG:31467\"",[211,729,730],{"class":220},")           ",[211,732,734],{"class":733},"sjoCn","# DHDN \u002F Gauss-Krüger zone 3\n",[211,736,737],{"class":105,"line":276},[211,738,260],{"emptyLinePlaceholder":259},[211,740,741,744,746],{"class":105,"line":302},[211,742,743],{"class":220},"ops ",[211,745,359],{"class":216},[211,747,748],{"class":220}," QgsDatumTransform.operations(source, target)\n",[211,750,751,753,756,758,761],{"class":105,"line":347},[211,752,513],{"class":216},[211,754,755],{"class":220}," i, op ",[211,757,285],{"class":216},[211,759,760],{"class":288}," enumerate",[211,762,763],{"class":220},"(ops):\n",[211,765,766,769,772,775,778,781,784,787,790,793,796,799,802,804,807],{"class":105,"line":353},[211,767,768],{"class":288},"    print",[211,770,771],{"class":220},"(i, ",[211,773,774],{"class":216},"f",[211,776,777],{"class":295},"\"±",[211,779,780],{"class":288},"{",[211,782,783],{"class":220},"op.accuracy",[211,785,786],{"class":288},"}",[211,788,789],{"class":295}," m\"",[211,791,792],{"class":216}," if",[211,794,795],{"class":220}," op.accuracy ",[211,797,798],{"class":216},">=",[211,800,801],{"class":288}," 0",[211,803,402],{"class":216},[211,805,806],{"class":295}," \"accuracy unknown\"",[211,808,809],{"class":220},",\n",[211,811,812,815,817,820,822,825],{"class":105,"line":371},[211,813,814],{"class":295},"          \"available\"",[211,816,792],{"class":216},[211,818,819],{"class":220}," op.isAvailable ",[211,821,429],{"class":216},[211,823,824],{"class":295}," \"MISSING GRID\"",[211,826,827],{"class":220},", op.name)\n",[211,829,830],{"class":105,"line":414},[211,831,260],{"emptyLinePlaceholder":259},[211,833,834,837,839,842,845,847,850,852,855,857],{"class":105,"line":438},[211,835,836],{"class":220},"best ",[211,838,359],{"class":216},[211,840,841],{"class":288}," next",[211,843,844],{"class":220},"(op ",[211,846,513],{"class":216},[211,848,849],{"class":220}," op ",[211,851,285],{"class":216},[211,853,854],{"class":220}," ops ",[211,856,391],{"class":216},[211,858,859],{"class":220}," op.isAvailable)\n",[211,861,862,865,867],{"class":105,"line":458},[211,863,864],{"class":220},"context ",[211,866,359],{"class":216},[211,868,869],{"class":220}," QgsCoordinateTransformContext()\n",[211,871,872],{"class":105,"line":473},[211,873,874],{"class":220},"context.addCoordinateOperation(source, target, best.proj)\n",[211,876,877,879,881,884],{"class":105,"line":488},[211,878,533],{"class":288},[211,880,587],{"class":220},[211,882,883],{"class":295},"\"using:\"",[211,885,886],{"class":220},", best.name)\n",[14,888,889,600,891,894,895,899],{},[597,890,599],{},[208,892,893],{},"QgsDatumTransform.operations"," lists every PROJ operation between the two systems with its accuracy and whether its grid files are present. Choosing the first available operation — PROJ orders them by accuracy — and pinning it in a transform context makes every layer use exactly that pipeline. If the best operation is marked as missing its grid, install the grid first, as covered in ",[21,896,898],{"href":897},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Finstall-proj-datum-grids-pyqgis\u002F","installing PROJ datum grids",", rather than silently falling back to a metre-level shift.",[168,901,903],{"id":902},"reproject-and-write-to-one-geopackage","Reproject and write to one GeoPackage",[14,905,906],{},"With the transformation fixed, each layer is reprojected and appended as a table to a single target GeoPackage, keeping its name.",[201,908,910],{"className":203,"code":909,"language":205,"meta":206,"style":206},"import processing\nfrom qgis.core import QgsProcessingContext, QgsProcessingFeedback\n\nout_gpkg = \"\u002Fdata\u002Fharmonised\u002Fall_layers_25832.gpkg\"\npctx = QgsProcessingContext()\npctx.setTransformContext(context)\nfirst, log = True, []\n\nfor name, uri, crs in inventory:\n    if crs == target:\n        layer = QgsVectorLayer(uri, name, \"ogr\")\n        action = \"copied\"\n    else:\n        layer = processing.run(\"native:reprojectlayer\", {\n            \"INPUT\": uri, \"TARGET_CRS\": target, \"OPERATION\": best.proj,\n            \"OUTPUT\": \"memory:\"}, context=pctx)[\"OUTPUT\"]\n        action = f\"reprojected from {crs.authid()}\"\n    processing.run(\"native:savefeatures\", {\n        \"INPUT\": layer, \"OUTPUT\": out_gpkg, \"LAYER_NAME\": name,\n        \"ACTION_ON_EXISTING_FILE\": 0 if first else 1})\n    first = False\n    log.append((name, action, layer.featureCount()))\n    print(f\"{name:\u003C30} {action:\u003C32} {layer.featureCount():>7}\")\n",[208,911,912,919,930,934,944,954,959,972,976,987,999,1012,1022,1030,1045,1065,1094,1116,1126,1145,1167,1177,1183],{"__ignoreMap":206},[211,913,914,916],{"class":105,"line":213},[211,915,224],{"class":216},[211,917,918],{"class":220}," processing\n",[211,920,921,923,925,927],{"class":105,"line":230},[211,922,217],{"class":216},[211,924,248],{"class":220},[211,926,224],{"class":216},[211,928,929],{"class":220}," QgsProcessingContext, QgsProcessingFeedback\n",[211,931,932],{"class":105,"line":243},[211,933,260],{"emptyLinePlaceholder":259},[211,935,936,939,941],{"class":105,"line":256},[211,937,938],{"class":220},"out_gpkg ",[211,940,359],{"class":216},[211,942,943],{"class":295}," \"\u002Fdata\u002Fharmonised\u002Fall_layers_25832.gpkg\"\n",[211,945,946,949,951],{"class":105,"line":263},[211,947,948],{"class":220},"pctx ",[211,950,359],{"class":216},[211,952,953],{"class":220}," QgsProcessingContext()\n",[211,955,956],{"class":105,"line":276},[211,957,958],{"class":220},"pctx.setTransformContext(context)\n",[211,960,961,964,966,969],{"class":105,"line":302},[211,962,963],{"class":220},"first, log ",[211,965,359],{"class":216},[211,967,968],{"class":288}," True",[211,970,971],{"class":220},", []\n",[211,973,974],{"class":105,"line":347},[211,975,260],{"emptyLinePlaceholder":259},[211,977,978,980,983,985],{"class":105,"line":353},[211,979,513],{"class":216},[211,981,982],{"class":220}," name, uri, crs ",[211,984,285],{"class":216},[211,986,566],{"class":220},[211,988,989,991,994,996],{"class":105,"line":371},[211,990,572],{"class":216},[211,992,993],{"class":220}," crs ",[211,995,396],{"class":216},[211,997,998],{"class":220}," target:\n",[211,1000,1001,1004,1006,1008,1010],{"class":105,"line":414},[211,1002,1003],{"class":220},"        layer ",[211,1005,359],{"class":216},[211,1007,466],{"class":220},[211,1009,365],{"class":295},[211,1011,368],{"class":220},[211,1013,1014,1017,1019],{"class":105,"line":438},[211,1015,1016],{"class":220},"        action ",[211,1018,359],{"class":216},[211,1020,1021],{"class":295}," \"copied\"\n",[211,1023,1024,1027],{"class":105,"line":458},[211,1025,1026],{"class":216},"    else",[211,1028,1029],{"class":220},":\n",[211,1031,1032,1034,1036,1039,1042],{"class":105,"line":473},[211,1033,1003],{"class":220},[211,1035,359],{"class":216},[211,1037,1038],{"class":220}," processing.run(",[211,1040,1041],{"class":295},"\"native:reprojectlayer\"",[211,1043,1044],{"class":220},", {\n",[211,1046,1047,1050,1053,1056,1059,1062],{"class":105,"line":488},[211,1048,1049],{"class":295},"            \"INPUT\"",[211,1051,1052],{"class":220},": uri, ",[211,1054,1055],{"class":295},"\"TARGET_CRS\"",[211,1057,1058],{"class":220},": target, ",[211,1060,1061],{"class":295},"\"OPERATION\"",[211,1063,1064],{"class":220},": best.proj,\n",[211,1066,1067,1070,1073,1076,1079,1083,1085,1088,1091],{"class":105,"line":497},[211,1068,1069],{"class":295},"            \"OUTPUT\"",[211,1071,1072],{"class":220},": ",[211,1074,1075],{"class":295},"\"memory:\"",[211,1077,1078],{"class":220},"}, ",[211,1080,1082],{"class":1081},"s9osk","context",[211,1084,359],{"class":216},[211,1086,1087],{"class":220},"pctx)[",[211,1089,1090],{"class":295},"\"OUTPUT\"",[211,1092,1093],{"class":220},"]\n",[211,1095,1096,1098,1100,1103,1106,1108,1111,1113],{"class":105,"line":502},[211,1097,1016],{"class":220},[211,1099,359],{"class":216},[211,1101,1102],{"class":216}," f",[211,1104,1105],{"class":295},"\"reprojected from ",[211,1107,780],{"class":288},[211,1109,1110],{"class":220},"crs.authid()",[211,1112,786],{"class":288},[211,1114,1115],{"class":295},"\"\n",[211,1117,1118,1121,1124],{"class":105,"line":530},[211,1119,1120],{"class":220},"    processing.run(",[211,1122,1123],{"class":295},"\"native:savefeatures\"",[211,1125,1044],{"class":220},[211,1127,1128,1131,1134,1136,1139,1142],{"class":105,"line":556},[211,1129,1130],{"class":295},"        \"INPUT\"",[211,1132,1133],{"class":220},": layer, ",[211,1135,1090],{"class":295},[211,1137,1138],{"class":220},": out_gpkg, ",[211,1140,1141],{"class":295},"\"LAYER_NAME\"",[211,1143,1144],{"class":220},": name,\n",[211,1146,1147,1150,1152,1154,1156,1159,1161,1164],{"class":105,"line":569},[211,1148,1149],{"class":295},"        \"ACTION_ON_EXISTING_FILE\"",[211,1151,1072],{"class":220},[211,1153,51],{"class":288},[211,1155,792],{"class":216},[211,1157,1158],{"class":220}," first ",[211,1160,429],{"class":216},[211,1162,1163],{"class":288}," 1",[211,1165,1166],{"class":220},"})\n",[211,1168,1169,1172,1174],{"class":105,"line":581},[211,1170,1171],{"class":220},"    first ",[211,1173,359],{"class":216},[211,1175,1176],{"class":288}," False\n",[211,1178,1180],{"class":105,"line":1179},22,[211,1181,1182],{"class":220},"    log.append((name, action, layer.featureCount()))\n",[211,1184,1186,1188,1190,1192,1195,1197,1200,1203,1205,1207,1210,1213,1215,1217,1220,1223,1225,1227],{"class":105,"line":1185},23,[211,1187,768],{"class":288},[211,1189,587],{"class":220},[211,1191,774],{"class":216},[211,1193,1194],{"class":295},"\"",[211,1196,780],{"class":288},[211,1198,1199],{"class":220},"name",[211,1201,1202],{"class":216},":\u003C30",[211,1204,786],{"class":288},[211,1206,317],{"class":288},[211,1208,1209],{"class":220},"action",[211,1211,1212],{"class":216},":\u003C32",[211,1214,786],{"class":288},[211,1216,317],{"class":288},[211,1218,1219],{"class":220},"layer.featureCount()",[211,1221,1222],{"class":216},":>7",[211,1224,786],{"class":288},[211,1226,1194],{"class":295},[211,1228,368],{"class":220},[14,1230,1231,1233,1234,1237,1238,1241],{},[597,1232,599],{}," Passing the PROJ pipeline string as ",[208,1235,1236],{},"OPERATION"," makes ",[208,1239,1240],{},"native:reprojectlayer"," use exactly the chosen transformation, independent of project settings; the transform context on the Processing context provides the same guarantee for any other step. Layers already in the target CRS are copied, not reprojected, so their coordinates are untouched. Writing every layer as a table in one GeoPackage gives a single, self-contained deliverable. Duplicate names across sources would overwrite each other — make names unique, for example by prefixing the source folder, before writing.",[168,1243,1245],{"id":1244},"keep-styles-with-the-layers","Keep styles with the layers",[14,1247,1248],{},"Reprojection creates new layers without the styling the originals had. GeoPackage can store styles alongside tables, so they can be copied across.",[201,1250,1252],{"className":203,"code":1251,"language":205,"meta":206,"style":206},"for name, uri, crs in inventory:\n    src = QgsVectorLayer(uri, name, \"ogr\")\n    qml = Path(uri.split(\"|\")[0]).with_suffix(\".qml\")\n    dst = QgsVectorLayer(f\"{out_gpkg}|layername={name}\", name, \"ogr\")\n    if qml.exists():\n        dst.loadNamedStyle(str(qml))\n    else:\n        dst.setRenderer(src.renderer().clone())\n    dst.saveStyleToDatabase(name, \"default style\", True, \"\")\n",[208,1253,1254,1264,1277,1303,1342,1349,1359,1365,1370],{"__ignoreMap":206},[211,1255,1256,1258,1260,1262],{"class":105,"line":213},[211,1257,513],{"class":216},[211,1259,982],{"class":220},[211,1261,285],{"class":216},[211,1263,566],{"class":220},[211,1265,1266,1269,1271,1273,1275],{"class":105,"line":230},[211,1267,1268],{"class":220},"    src ",[211,1270,359],{"class":216},[211,1272,466],{"class":220},[211,1274,365],{"class":295},[211,1276,368],{"class":220},[211,1278,1279,1282,1284,1287,1290,1293,1295,1298,1301],{"class":105,"line":243},[211,1280,1281],{"class":220},"    qml ",[211,1283,359],{"class":216},[211,1285,1286],{"class":220}," Path(uri.split(",[211,1288,1289],{"class":295},"\"|\"",[211,1291,1292],{"class":220},")[",[211,1294,51],{"class":288},[211,1296,1297],{"class":220},"]).with_suffix(",[211,1299,1300],{"class":295},"\".qml\"",[211,1302,368],{"class":220},[211,1304,1305,1308,1310,1313,1315,1317,1319,1322,1324,1327,1329,1331,1333,1335,1338,1340],{"class":105,"line":256},[211,1306,1307],{"class":220},"    dst ",[211,1309,359],{"class":216},[211,1311,1312],{"class":220}," QgsVectorLayer(",[211,1314,774],{"class":216},[211,1316,1194],{"class":295},[211,1318,780],{"class":288},[211,1320,1321],{"class":220},"out_gpkg",[211,1323,786],{"class":288},[211,1325,1326],{"class":295},"|layername=",[211,1328,780],{"class":288},[211,1330,1199],{"class":220},[211,1332,786],{"class":288},[211,1334,1194],{"class":295},[211,1336,1337],{"class":220},", name, ",[211,1339,365],{"class":295},[211,1341,368],{"class":220},[211,1343,1344,1346],{"class":105,"line":263},[211,1345,572],{"class":216},[211,1347,1348],{"class":220}," qml.exists():\n",[211,1350,1351,1354,1356],{"class":105,"line":276},[211,1352,1353],{"class":220},"        dst.loadNamedStyle(",[211,1355,385],{"class":288},[211,1357,1358],{"class":220},"(qml))\n",[211,1360,1361,1363],{"class":105,"line":302},[211,1362,1026],{"class":216},[211,1364,1029],{"class":220},[211,1366,1367],{"class":105,"line":347},[211,1368,1369],{"class":220},"        dst.setRenderer(src.renderer().clone())\n",[211,1371,1372,1375,1378,1380,1383,1385,1388],{"class":105,"line":353},[211,1373,1374],{"class":220},"    dst.saveStyleToDatabase(name, ",[211,1376,1377],{"class":295},"\"default style\"",[211,1379,323],{"class":220},[211,1381,1382],{"class":288},"True",[211,1384,323],{"class":220},[211,1386,1387],{"class":295},"\"\"",[211,1389,368],{"class":220},[14,1391,1392,1394,1395,1398,1399,1402,1403,30],{},[597,1393,599],{}," A sidecar QML next to the source file takes priority; otherwise the source layer's current renderer is cloned. ",[208,1396,1397],{},"saveStyleToDatabase"," writes the style into the GeoPackage's ",[208,1400,1401],{},"layer_styles"," table and marks it as default, so the layer opens styled anywhere. More on database-stored styles in ",[21,1404,1406],{"href":1405},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-styles-to-database-pyqgis\u002F","saving styles to a database",[168,1408,1410],{"id":1409},"verify-positions-with-a-check-point","Verify positions with a check point",[14,1412,1413],{},"A wrong transformation shifts data by metres, which is invisible at most zoom levels. A point with known coordinates in both systems — a survey benchmark, a trig point — verifies the result numerically.",[14,1415,1416],{},[34,1417,1420,1423,1426,1429,1436,1439,1442,1446,1450,1454,1459,1463,1468,1472,1476,1480,1484,1487],{"viewBox":1418,"role":37,"ariaLabel":1419,"xmlns":39},"0 0 760 196","A benchmark transformed with the chosen operation and compared against its published target coordinates to confirm accuracy",[41,1421,1422],{},"A check point in both systems",[45,1424,1425],{},"A benchmark with published coordinates in the source system is transformed with the chosen operation and compared with its published coordinates in the target system. A difference within the expected accuracy, here a few centimetres, confirms the transformation; a difference of metres means a weaker transformation or a missing grid was used.",[49,1427],{"x":51,"y":51,"width":52,"height":1428,"fill":54},"196",[56,1430,1431],{},[59,1432,1434],{"id":1433,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"brCheckArrow",[68,1435],{"d":70,"fill":71},[73,1437,1438],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Known point, known answer",[49,1440],{"x":83,"y":84,"width":631,"height":1441,"rx":63,"fill":86,"stroke":87,"style":88},"120",[73,1443,1445],{"x":108,"y":1444,"style":93,"fill":78,"textAnchor":79},"97.78","benchmark",[73,1447,1449],{"x":108,"y":1448,"style":98,"fill":87,"textAnchor":79},"123.78","source coords",[73,1451,1453],{"x":108,"y":1452,"style":98,"fill":87,"textAnchor":79},"149.78","published",[105,1455],{"x1":1456,"y1":1441,"x2":1457,"y2":1441,"stroke":71,"style":1458},"246","288","stroke-width:1.8;marker-end:url(#brCheckArrow)",[49,1460],{"x":1461,"y":84,"width":1462,"height":1441,"rx":63,"fill":134,"stroke":135,"style":88},"292","200",[73,1464,1467],{"x":1465,"y":1466,"style":93,"fill":135,"textAnchor":79},"392","110.78","transform",[73,1469,1471],{"x":1465,"y":1470,"style":98,"fill":87,"textAnchor":79},"136.78","chosen operation",[105,1473],{"x1":1474,"y1":1441,"x2":1475,"y2":1441,"stroke":71,"style":1458},"492","534",[49,1477],{"x":1478,"y":84,"width":1479,"height":1441,"rx":63,"fill":154,"stroke":155,"style":88},"538","198",[73,1481,1483],{"x":1482,"y":1444,"style":93,"fill":159,"textAnchor":79},"637","compare",[73,1485,1486],{"x":1482,"y":1448,"style":98,"fill":87,"textAnchor":79},"target coords",[73,1488,1489],{"x":1482,"y":1452,"style":98,"fill":87,"textAnchor":79},"Δ ≤ accuracy?",[201,1491,1493],{"className":203,"code":1492,"language":205,"meta":206,"style":206},"from qgis.core import QgsCoordinateTransform, QgsPointXY\n\nxf = QgsCoordinateTransform(source, target, context)\nbenchmark_src = QgsPointXY(3_478_912.410, 5_421_337.880)       # published DHDN GK3\nbenchmark_dst = QgsPointXY(478_853.973, 5_419_603.112)         # published ETRS89 UTM32\ngot = xf.transform(benchmark_src)\ndelta = got.distance(benchmark_dst)\nprint(f\"check point difference: {delta:.3f} m\")\nassert delta \u003C 0.10, \"transformation less accurate than expected\"\n",[208,1494,1495,1506,1510,1520,1544,1567,1577,1587,1612],{"__ignoreMap":206},[211,1496,1497,1499,1501,1503],{"class":105,"line":213},[211,1498,217],{"class":216},[211,1500,248],{"class":220},[211,1502,224],{"class":216},[211,1504,1505],{"class":220}," QgsCoordinateTransform, QgsPointXY\n",[211,1507,1508],{"class":105,"line":230},[211,1509,260],{"emptyLinePlaceholder":259},[211,1511,1512,1515,1517],{"class":105,"line":243},[211,1513,1514],{"class":220},"xf ",[211,1516,359],{"class":216},[211,1518,1519],{"class":220}," QgsCoordinateTransform(source, target, context)\n",[211,1521,1522,1525,1527,1530,1533,1535,1538,1541],{"class":105,"line":256},[211,1523,1524],{"class":220},"benchmark_src ",[211,1526,359],{"class":216},[211,1528,1529],{"class":220}," QgsPointXY(",[211,1531,1532],{"class":288},"3_478_912.410",[211,1534,323],{"class":220},[211,1536,1537],{"class":288},"5_421_337.880",[211,1539,1540],{"class":220},")       ",[211,1542,1543],{"class":733},"# published DHDN GK3\n",[211,1545,1546,1549,1551,1553,1556,1558,1561,1564],{"class":105,"line":263},[211,1547,1548],{"class":220},"benchmark_dst ",[211,1550,359],{"class":216},[211,1552,1529],{"class":220},[211,1554,1555],{"class":288},"478_853.973",[211,1557,323],{"class":220},[211,1559,1560],{"class":288},"5_419_603.112",[211,1562,1563],{"class":220},")         ",[211,1565,1566],{"class":733},"# published ETRS89 UTM32\n",[211,1568,1569,1572,1574],{"class":105,"line":276},[211,1570,1571],{"class":220},"got ",[211,1573,359],{"class":216},[211,1575,1576],{"class":220}," xf.transform(benchmark_src)\n",[211,1578,1579,1582,1584],{"class":105,"line":302},[211,1580,1581],{"class":220},"delta ",[211,1583,359],{"class":216},[211,1585,1586],{"class":220}," got.distance(benchmark_dst)\n",[211,1588,1589,1591,1593,1595,1598,1600,1603,1606,1608,1610],{"class":105,"line":347},[211,1590,533],{"class":288},[211,1592,587],{"class":220},[211,1594,774],{"class":216},[211,1596,1597],{"class":295},"\"check point difference: ",[211,1599,780],{"class":288},[211,1601,1602],{"class":220},"delta",[211,1604,1605],{"class":216},":.3f",[211,1607,786],{"class":288},[211,1609,789],{"class":295},[211,1611,368],{"class":220},[211,1613,1614,1617,1620,1623,1626,1628],{"class":105,"line":353},[211,1615,1616],{"class":216},"assert",[211,1618,1619],{"class":220}," delta ",[211,1621,1622],{"class":216},"\u003C",[211,1624,1625],{"class":288}," 0.10",[211,1627,323],{"class":220},[211,1629,1630],{"class":295},"\"transformation less accurate than expected\"\n",[14,1632,1633,1635],{},[597,1634,599],{}," Transforming a benchmark with the same context the batch used and comparing with its published target coordinates tests the whole chain. A difference of a few centimetres confirms a grid-based transformation; a metre or more means a Helmert fallback was used. Use a benchmark inside the data's area, since grid-based accuracy varies across a country. The coordinates above are illustrative; take real ones from your mapping agency.",[168,1637,1639],{"id":1638},"write-the-log","Write the log",[14,1641,1642],{},"A batch that changed coordinates of a hundred layers should leave a record: what was done to each layer, with which transformation, and how many features went through.",[201,1644,1646],{"className":203,"code":1645,"language":205,"meta":206,"style":206},"import csv\nfrom datetime import datetime\n\nwith open(out_gpkg.replace(\".gpkg\", \"_log.csv\"), \"w\", newline=\"\", encoding=\"utf-8\") as fh:\n    w = csv.writer(fh)\n    w.writerow([\"layer\", \"action\", \"features\", \"target\", \"operation\", \"run\"])\n    for name, action, count in log:\n        w.writerow([name, action, count, target.authid(), best.name,\n                    datetime.now().isoformat(timespec=\"seconds\")])\n",[208,1647,1648,1655,1667,1671,1723,1733,1769,1781,1786],{"__ignoreMap":206},[211,1649,1650,1652],{"class":105,"line":213},[211,1651,224],{"class":216},[211,1653,1654],{"class":220}," csv\n",[211,1656,1657,1659,1662,1664],{"class":105,"line":230},[211,1658,217],{"class":216},[211,1660,1661],{"class":220}," datetime ",[211,1663,224],{"class":216},[211,1665,1666],{"class":220}," datetime\n",[211,1668,1669],{"class":105,"line":243},[211,1670,260],{"emptyLinePlaceholder":259},[211,1672,1673,1676,1679,1682,1684,1686,1689,1692,1695,1697,1700,1702,1704,1706,1709,1711,1714,1717,1720],{"class":105,"line":256},[211,1674,1675],{"class":216},"with",[211,1677,1678],{"class":288}," open",[211,1680,1681],{"class":220},"(out_gpkg.replace(",[211,1683,326],{"class":295},[211,1685,323],{"class":220},[211,1687,1688],{"class":295},"\"_log.csv\"",[211,1690,1691],{"class":220},"), ",[211,1693,1694],{"class":295},"\"w\"",[211,1696,323],{"class":220},[211,1698,1699],{"class":1081},"newline",[211,1701,359],{"class":216},[211,1703,1387],{"class":295},[211,1705,323],{"class":220},[211,1707,1708],{"class":1081},"encoding",[211,1710,359],{"class":216},[211,1712,1713],{"class":295},"\"utf-8\"",[211,1715,1716],{"class":220},") ",[211,1718,1719],{"class":216},"as",[211,1721,1722],{"class":220}," fh:\n",[211,1724,1725,1728,1730],{"class":105,"line":263},[211,1726,1727],{"class":220},"    w ",[211,1729,359],{"class":216},[211,1731,1732],{"class":220}," csv.writer(fh)\n",[211,1734,1735,1738,1741,1743,1746,1748,1751,1753,1756,1758,1761,1763,1766],{"class":105,"line":276},[211,1736,1737],{"class":220},"    w.writerow([",[211,1739,1740],{"class":295},"\"layer\"",[211,1742,323],{"class":220},[211,1744,1745],{"class":295},"\"action\"",[211,1747,323],{"class":220},[211,1749,1750],{"class":295},"\"features\"",[211,1752,323],{"class":220},[211,1754,1755],{"class":295},"\"target\"",[211,1757,323],{"class":220},[211,1759,1760],{"class":295},"\"operation\"",[211,1762,323],{"class":220},[211,1764,1765],{"class":295},"\"run\"",[211,1767,1768],{"class":220},"])\n",[211,1770,1771,1773,1776,1778],{"class":105,"line":302},[211,1772,279],{"class":216},[211,1774,1775],{"class":220}," name, action, count ",[211,1777,285],{"class":216},[211,1779,1780],{"class":220}," log:\n",[211,1782,1783],{"class":105,"line":347},[211,1784,1785],{"class":220},"        w.writerow([name, action, count, target.authid(), best.name,\n",[211,1787,1788,1791,1794,1796,1799],{"class":105,"line":353},[211,1789,1790],{"class":220},"                    datetime.now().isoformat(",[211,1792,1793],{"class":1081},"timespec",[211,1795,359],{"class":216},[211,1797,1798],{"class":295},"\"seconds\"",[211,1800,1801],{"class":220},")])\n",[14,1803,1804,1806],{},[597,1805,599],{}," The log answers the questions that come up months later — was this layer transformed, with what — without anyone having to reconstruct the run. Including the operation name records the datum transformation, which is the detail most often lost and most often responsible for small, puzzling offsets between datasets.",[168,1808,1810],{"id":1809},"qgis-version-compatibility","QGIS version compatibility",[14,1812,1813,1815,1816,1818,1819,1821,1822,1824],{},[208,1814,1240],{}," with the ",[208,1817,1236],{}," parameter, ",[208,1820,893],{}," and transform contexts are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. ",[208,1823,603],{}," on provider metadata exists from 3.22. The list of operations depends on the PROJ version and installed grids.",[168,1826,1828],{"id":1827},"troubleshooting","Troubleshooting",[173,1830,1831,1837,1843,1849],{},[176,1832,1833,1836],{},[597,1834,1835],{},"Layers end up hundreds of metres off."," A source CRS was wrong; fix the label before reprojecting.",[176,1838,1839,1842],{},[597,1840,1841],{},"Differences of a metre or two between machines."," Different default transformations; pin the operation.",[176,1844,1845,1848],{},[597,1846,1847],{},"Layers overwrite each other in the GeoPackage."," Duplicate layer names; make them unique.",[176,1850,1851,1854],{},[597,1852,1853],{},"Styles are lost."," Copy QML or renderers and save them to the GeoPackage.",[168,1856,1858],{"id":1857},"conclusion","Conclusion",[14,1860,1861],{},"Inventory every layer's CRS, fix missing or wrong labels first, list and pin one datum transformation, reproject with that operation into one GeoPackage while skipping layers already in the target, carry styles across, verify with a benchmark, and log every layer with the transformation used.",[168,1863,1865],{"id":1864},"frequently-asked-questions","Frequently Asked Questions",[14,1867,1868,1871],{},[597,1869,1870],{},"Do I need to reproject if QGIS displays everything correctly?","\nFor display, no. For analysis, exchange and storage, a single CRS avoids repeated on-the-fly transformations and their subtle differences.",[14,1873,1874,1877],{},[597,1875,1876],{},"Can I reproject in place?","\nNot safely. Write new outputs and keep the originals until verified.",[14,1879,1880,1883,1884,1887,1888,30],{},[597,1881,1882],{},"What about layers in a PostGIS database?","\nUse ",[208,1885,1886],{},"ST_Transform"," in SQL, or reproject with Processing and import, as in ",[21,1889,1891],{"href":1890},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fimport-layer-into-postgis-pyqgis\u002F","importing a layer into PostGIS",[14,1893,1894,1897],{},[597,1895,1896],{},"How do I handle sources in several different CRSs?","\nList operations for each source–target pair, pin one per pair in the transform context, and pass the matching pipeline when reprojecting each layer. The log should then record the operation per layer rather than one for the whole run.",[14,1899,1900,1903],{},[597,1901,1902],{},"Does reprojection change attribute values?","\nNo. Only geometry changes — but stored area or length fields become stale and should be recomputed.",[168,1905,1907],{"id":1906},"related","Related",[173,1909,1910,1915,1920,1925,1931],{},[176,1911,1912,1914],{},[21,1913,24],{"href":23}," — the guide this recipe belongs to",[176,1916,1917],{},[21,1918,1919],{"href":28},"Batch Reprojecting Raster Datasets",[176,1921,1922],{},[21,1923,1924],{"href":897},"Install PROJ Datum Grids for PyQGIS",[176,1926,1927],{},[21,1928,1930],{"href":1929},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fset-project-crs-and-datum-transform-pyqgis\u002F","Set Project CRS and Datum Transform in PyQGIS",[176,1932,1933],{},[21,1934,1936],{"href":1935},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-algorithm-over-folder-of-files-pyqgis\u002F","Run an Algorithm Over a Folder of Files in PyQGIS",[1938,1939,1940],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":206,"searchDepth":230,"depth":230,"links":1942},[1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954],{"id":170,"depth":230,"text":171},{"id":195,"depth":230,"text":196},{"id":612,"depth":230,"text":613},{"id":902,"depth":230,"text":903},{"id":1244,"depth":230,"text":1245},{"id":1409,"depth":230,"text":1410},{"id":1638,"depth":230,"text":1639},{"id":1809,"depth":230,"text":1810},{"id":1827,"depth":230,"text":1828},{"id":1857,"depth":230,"text":1858},{"id":1864,"depth":230,"text":1865},{"id":1906,"depth":230,"text":1907},"Reproject every vector layer in a folder, GeoPackage or project to one target CRS with native:reprojectlayer — choosing the datum transformation explicitly, preserving layer names and styles, skipping layers already in the target, verifying positions and logging what changed.","md",{"slug":1958,"type":1959,"breadcrumb":1960,"datePublished":1961,"dateModified":1961},"batch-reproject-vector-layers-pyqgis","article","Batch Reproject Vector Layers","2026-10-02","\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reproject-vector-layers-pyqgis",{"title":5,"description":1955},"spatial-data-processing-automation\u002Fcoordinate-reference-systems\u002Fbatch-reproject-vector-layers-pyqgis\u002Findex","wGmkBZseuUBNw5A3bFMCvivy8o8j2t7unkDO7Lx3XP0",1790966261121]