├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── check_topology.md ├── fdw.md ├── filter_data.md ├── grant.md ├── group_data.md ├── import_data.md ├── index.md ├── join_data.md ├── links_and_data.md ├── logo.svg ├── media │ ├── qgis_connexion_PostgreSQL.png │ ├── qgis_creer_schema_explorateur.png │ ├── qgis_creer_table_explorateur.png │ ├── qgis_rendu_simplification_fournisseur.png │ ├── qgis_traitement_exporter_dialogue_algorithme.png │ └── qgis_traitement_exporter_postgresql_ogr.png ├── merge_geometries.md ├── perform_calculation.md ├── postgresql_in_qgis.md ├── save_queries.md ├── sql_select.md ├── triggers.md ├── tutoriel.md ├── union.md ├── utils.md └── validate_geometries.md ├── mkdocs.yml └── requirements.txt /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: 📖 Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Get source code 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Set up Python 3.10 19 | uses: actions/setup-python@v4.5.0 20 | with: 21 | python-version: '3.10' 22 | 23 | - name: Set up NodeJS (for search index prebuilding) 24 | uses: actions/setup-node@v3.6.0 25 | with: 26 | node-version: '12' 27 | 28 | - name: Cache project dependencies (pip) 29 | uses: actions/cache@v3.2.6 30 | with: 31 | path: ~/.cache/pip 32 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 33 | restore-keys: | 34 | ${{ runner.os }}-pip- 35 | ${{ runner.os }}- 36 | 37 | - name: Install dependencies 38 | run: | 39 | python -m pip install --upgrade pip setuptools wheel 40 | python -m pip install -r requirements.txt 41 | 42 | - name: Deploy to Github Pages 43 | run: | 44 | git config --global user.name "${{ secrets.BOT_NAME }}" 45 | git config --global user.email "${{ secrets.BOT_MAIL }}" 46 | mkdocs gh-deploy --clean --force --verbose 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .venv/ 3 | build/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formation PostGIS 2 | 3 | Visible sur https://docs.3liz.org/formation-postgis/ 4 | 5 | ![logo](docs/logo.svg) 6 | -------------------------------------------------------------------------------- /docs/check_topology.md: -------------------------------------------------------------------------------- 1 | # Vérifier la topologie 2 | 3 | ## Déplacer les nœuds sur une grille 4 | 5 | Avant de vérifier la topologie, il faut au préalable avoir des géométries valides (cf. chapitre précédent). 6 | 7 | Certaines micro-erreurs de topologie peuvent peuvent être corrigées en réalisant une simplification des données à l'aide d'une grille, par exemple pour corriger des soucis d'arrondis. Pour cela, PostGIS a une fonction **ST_SnapToGrid**. 8 | 9 | On peut utiliser conjointement **ST_Simplify** et **ST_SnapToGrid** pour effectuer une première correction sur les données. Attention, ces fonctions modifient la donnée. A vous de choisir la bonne tolérance, par exemple 5 cm, qui dépend de votre donnée et de votre cas d'utilisation. 10 | 11 | Tester la simplification en lançant la requête suivante, et en chargeant le résultat comme une nouvelle couche dans QGIS 12 | 13 | ```sql 14 | SELECT 15 | ST_Multi( 16 | ST_CollectionExtract( 17 | ST_MakeValid( 18 | ST_SnapToGrid( 19 | st_simplify(geom,0), 20 | 0.05 -- 5 cm 21 | ) 22 | ), 23 | 3 24 | ) 25 | )::geometry(multipolygon, 2154) 26 | FROM z_formation.parcelle_havre 27 | ; 28 | ``` 29 | 30 | Une fois le résultat visuellement testé dans QGIS, par comparaison avec la table source, on peut choisir de *modifier la géométrie de la table* avec la version simplifiée des données: 31 | 32 | ```sql 33 | -- Parcelles 34 | UPDATE z_formation.parcelle_havre 35 | SET geom = 36 | ST_Multi( 37 | ST_CollectionExtract( 38 | ST_MakeValid( 39 | ST_SnapToGrid( 40 | st_simplify(geom,0), 41 | 0.05 -- 5 cm 42 | ) 43 | ), 44 | 3 45 | ) 46 | ) 47 | ; 48 | ; 49 | ``` 50 | 51 | **Attention:** Si vous avez d'autres tables avec des objets en relation spatiale avec cette table, il faut aussi effectuer le même traitement pour que les géométries de toutes les couches se calent sur la même grille. Par exemple la table des zonages. 52 | 53 | ```sql 54 | UPDATE z_formation.zone_urba 55 | SET geom = 56 | ST_Multi( 57 | ST_CollectionExtract( 58 | ST_MakeValid( 59 | ST_SnapToGrid( 60 | st_simplify(geom,0), 61 | 0.05 -- 5 cm 62 | ) 63 | ), 64 | 3 65 | ) 66 | ) 67 | ; 68 | ``` 69 | 70 | 71 | ## Repérer certaines erreurs de topologies 72 | 73 | PostGIS possède de nombreuses fonctions de **relations spatiales** qui permettent de trouver les objets qui se chevauchent, qui se touchent, etc. Ces fonctions peuvent être utilisées pour comparer les objets d'une même table, ou de deux tables différentes. Voir: https://postgis.net/docs/reference.html#Spatial_Relationships_Measurements 74 | 75 | Par exemple, trouver les parcelles voisines qui se recouvrent: on utilise la fonction **ST_Overlaps**. On peut créer une couche listant les recouvrements: 76 | 77 | 78 | ```sql 79 | DROP TABLE IF EXISTS z_formation.recouvrement_parcelle_voisines; 80 | CREATE TABLE z_formation.recouvrement_parcelle_voisines AS 81 | SELECT DISTINCT ON (geom) 82 | parcelle_a, parcelle_b, aire_a, aire_b, ST_Area(geom) AS aire, geom 83 | FROM ( 84 | SELECT 85 | a.id_parcelle AS parcelle_a, ST_Area(a.geom) AS aire_a, 86 | b.id_parcelle AS parcelle_b, ST_Area(b.geom) AS aire_b, 87 | (ST_Multi( 88 | st_collectionextract( 89 | ST_MakeValid(ST_Intersection(a.geom, b.geom)) 90 | , 3) 91 | ))::geometry(MultiPolygon,2154) AS geom 92 | FROM z_formation.parcelle_havre AS a 93 | JOIN z_formation.parcelle_havre AS b 94 | ON a.id_parcelle != b.id_parcelle 95 | --ON ST_Intersects(a.geom, b.geom) 96 | AND ST_Overlaps(a.geom, b.geom) 97 | ) AS voisin 98 | ORDER BY geom 99 | ; 100 | 101 | CREATE INDEX ON z_formation.recouvrement_parcelle_voisines USING GIST (geom); 102 | 103 | ``` 104 | 105 | On peut alors ouvrir cette couche dans QGIS pour zoomer sur chaque objet de recouvrement. 106 | 107 | Récupérer la liste des identifiants de ces parcelles: 108 | 109 | ```sql 110 | SELECT string_agg( parcelle_a::text, ',') FROM z_formation.recouvrement_parcelle_voisines; 111 | ``` 112 | 113 | On peut utiliser le résultat de cette requête pour sélectionner les parcelles problématiques: on sélectionne le résultat dans le tableau du gestionnaire de base de données, et on copie (CTRL + C). On peut alors utiliser cette liste dans une **sélection par expression** dans QGIS, avec par exemple l'expression 114 | 115 | ```sql 116 | "id_parcelle" IN ( 117 | 729091,742330,742783,742513,742514,743114,742992,742578,742991,742544,743009,744282,744378,744378,744281,744199,743646,746445,743680,744280, 118 | 743653,743812,743208,743812,743813,744199,694298,694163,721712,707463,744412,707907,707069,721715,721715,696325,696372,746305,722156,722555, 119 | 722195,714500,715969,722146,722287,723526,720296,720296,722296,723576,723572,723572,723571,724056,723570,723568,740376,722186,724055,714706, 120 | 723413,723988,721808,721808,723413,724064,723854,723854,724063,723518,720736,720653,741079,741227,740932,740932,740891,721259,741304,741304, 121 | 741501,741226,741812) 122 | ``` 123 | 124 | Une fois les parcelles sélectionnées, on peut utiliser certains outils de QGIS pour faciliter la correction: 125 | 126 | * plugin **Vérifier les géométries** en cochant la case **Uniquement les entités sélectionnées** 127 | * plugin **Accrochage de géométrie** 128 | * plugin **Go 2 next feature** pour facilement zoomer d'objets en objets 129 | 130 | 131 | ## Accrocher les géométries sur d'autres géométries 132 | 133 | Dans PostGIS, on peut utiliser la fonction **ST_Snap** dans une requête SQL pour déplacer les nœuds d'une géométrie et les coller sur ceux d'une autre. 134 | 135 | Par exemple, coller les géométries choisies (via identifiants dans le WHERE) de la table de zonage sur les parcelles choisies (via identifiants dans le WHERE): 136 | 137 | ```sql 138 | WITH a AS ( 139 | SELECT DISTINCT z.id_zone_urba, 140 | st_force2d( 141 | ST_Multi( 142 | ST_Snap( 143 | ST_Simplify(z.geom, 1), 144 | ST_Collect(p.geom), 145 | 0.5 146 | ) 147 | ) 148 | ) AS geom 149 | FROM z_formation.parcelle_havre AS p 150 | INNER JOIN z_formation.zone_urba AS z 151 | ON st_dwithin(z.geom, p.geom, 0.5) 152 | WHERE TRUE 153 | AND z.id_zone_urba IN (113,29) 154 | AND p.id_parcelle IN (711337,711339,711240,711343) 155 | GROUP BY z.id_zone_urba 156 | ) 157 | UPDATE z_formation.zone_urba pz 158 | SET geom = a.geom 159 | FROM a 160 | WHERE pz.id_zone_urba = a.id_zone_urba 161 | ``` 162 | 163 | **Attention:** Cette fonction ne sait coller qu'**aux nœuds** de la table de référence, pas aux segments. Il serait néanmoins possible de créer automatiquement les nœuds situés sur la projection du nœud à déplacer sur la géométrie de référence. 164 | 165 | Dans la pratique, il est très souvent fastidieux de corriger les erreurs de topologie d'une couche. Les outils automatiques ( Vérifier les géométries de QGIS ou outil v.clean de Grass) ne permettent pas toujours de bien voir ce qui a été modifié. 166 | 167 | Au contraire, une modification manuelle est plus précise, mais prend beaucoup de temps. 168 | 169 | Le Ministère du Développement Durable a mis en ligne un document intéressant sur les outils disponibles dans QGIS, OpenJump et PostgreSQL pour valider et corriger les géométries: http://www.geoinformations.developpement-durable.gouv.fr/verification-et-corrections-des-geometries-a3522.html 170 | -------------------------------------------------------------------------------- /docs/fdw.md: -------------------------------------------------------------------------------- 1 | # Accéder à des données externes : les Foreign Data Wrapper (FDW) 2 | 3 | L'utilisation d'un FDW permet de **consulter des données externes** à la base comme si elles étaient stockées dans des tables. On peut lancer des requêtes pour récupérer seulement certains champs, filtrer les données, etc. 4 | 5 | Des **tables étrangères** sont créées, qui pointent vers les données externes. A chaque requête sur ces tables, PostgreSQL récupère les données depuis la connexion au serveur externe. 6 | 7 | On passe classiquement par les étapes suivantes: 8 | 9 | * Ajout de l'**extension** correspondant au format souhaité: `postgres_fdw` (bases PostgreSQL externes), `ogr_fdw` (données vectorielles via ogr2ogr), etc. 10 | * Création d'un **serveur** qui permet de configurer les informations de connexion au serveur externe 11 | * Création optionnelle d'un **schéma** pour y stocker les tables de ce serveur 12 | * Création manuelle ou automatique de **tables étrangères** qui pointent vers les données externes 13 | * **Requêtes** sur ces tables étrangères 14 | 15 | 16 | ## Le FDW ogr_fdw pour lire des données vectorielles 17 | 18 | Avec ce Foreign Data Wrapper **ogr_fdw**, on peut appeler n'importe quelle source de données externe compatible avec la librairie **ogr2ogr** et les exploiter comme des tables: fichiers GeoJSON ou Shapefile, GPX, CSV, mais aussi les protocoles comme le WFS. 19 | 20 | Voir la [documentation officielle de ogr_fdw](https://github.com/pramsey/pgsql-ogr-fdw). 21 | 22 | ### Installation 23 | 24 | Pour l'installer sur une machine **Linux**, il suffit d'installer le paquet correspondant à la version de PostgreSQL, par exemple `postgresql-11-ogr-fdw`. 25 | 26 | Sous **Windows**, il est disponible avec le paquet PostGIS via l'outil [StackBuilder](https://www.postgresql.org/download/windows/). 27 | 28 | ### Exemple d'utilisation: récupérer des couches d'un serveur WFS 29 | 30 | Nous allons utiliser le FDW pour récupérer des données mises à disposition sur le serveur de l'INPN via le protocole WFS. 31 | 32 | Vous pouvez d'abord tester dans QGIS quelles données sont disponibles sur ce serveur en créant une nouvelle connexion WFS avec l'URL `http://ws.carmencarto.fr/WFS/119/fxx_inpn?` 33 | 34 | Via QGIS ou un autre client à la base de données, nous pouvons maintenant montrer comment récupérer ces données: 35 | 36 | * Ajouter l'**extension** `ogr_fdw`: 37 | 38 | ```sql 39 | -- Ajouter l'extension pour lire des fichiers SIG 40 | -- Cette commande doit être lancée par un super utilisateur (ou un utilisateur ayant le droit de le faire) 41 | CREATE EXTENSION IF NOT EXISTS ogr_fdw; 42 | ``` 43 | 44 | * Créer le **serveur** de données: 45 | 46 | ```sql 47 | -- Créer le serveur 48 | DROP SERVER IF EXISTS fdw_ogr_inpn_metropole; 49 | CREATE SERVER fdw_ogr_inpn_metropole FOREIGN DATA WRAPPER ogr_fdw 50 | OPTIONS ( 51 | datasource 'WFS:http://ws.carmencarto.fr/WFS/119/fxx_inpn?', 52 | format 'WFS' 53 | ); 54 | ``` 55 | 56 | * Créer un **schéma** pour y stocker les tables étrangères: 57 | 58 | ```sql 59 | -- Créer un schéma pour la dreal 60 | CREATE SCHEMA IF NOT EXISTS inpn_metropole; 61 | ``` 62 | 63 | * Créer automatiquement les **tables étrangères** qui "pointent" vers les couches du WFS, via la commande `IMPORT SCHEMA`: 64 | 65 | ```sql 66 | -- Récupérer l'ensemble des couches WFS comme des tables dans le schéma ref_dreal 67 | IMPORT FOREIGN SCHEMA ogr_all 68 | FROM SERVER fdw_ogr_inpn_metropole 69 | INTO inpn_metropole 70 | OPTIONS ( 71 | -- mettre le nom des tables en minuscule et sans caractères bizarres 72 | launder_table_names 'true', 73 | -- mettre le nom des champs en minuscule 74 | launder_column_names 'true' 75 | ) 76 | ; 77 | ``` 78 | 79 | * Lister les tables récupérées 80 | 81 | ```sql 82 | SELECT foreign_table_schema, foreign_table_name 83 | FROM information_schema.foreign_tables 84 | WHERE foreign_table_schema = 'inpn_metropole' 85 | ORDER BY foreign_table_schema, foreign_table_name; 86 | ``` 87 | 88 | ce qui montre: 89 | 90 | | foreign_table_schema | foreign_table_name | 91 | |----------------------|--------------------------------------------------| 92 | | inpn_metropole | arretes_de_protection_de_biotope | 93 | | inpn_metropole | arretes_de_protection_de_geotope | 94 | | inpn_metropole | bien_du_patrimoine_mondial_de_l_unesco | 95 | | inpn_metropole | geoparcs | 96 | | inpn_metropole | ospar | 97 | | inpn_metropole | parc_naturel_marin | 98 | | inpn_metropole | parcs_nationaux | 99 | | inpn_metropole | parcs_naturels_regionaux | 100 | | inpn_metropole | reserves_biologiques | 101 | | inpn_metropole | reserves_de_la_biosphere | 102 | | inpn_metropole | reserves_integrales_de_parcs_nationaux | 103 | | inpn_metropole | reserves_nationales_de_chasse_et_faune_sauvage | 104 | | inpn_metropole | reserves_naturelles_nationales | 105 | | inpn_metropole | reserves_naturelles_regionales | 106 | | inpn_metropole | rnc | 107 | | inpn_metropole | sites_d_importance_communautaire | 108 | | inpn_metropole | sites_d_importance_communautaire_joue__zsc_sic_ | 109 | | inpn_metropole | sites_ramsar | 110 | | inpn_metropole | terrains_des_conservatoires_des_espaces_naturels | 111 | | inpn_metropole | terrains_du_conservatoire_du_littoral | 112 | | inpn_metropole | zico | 113 | | inpn_metropole | znieff1 | 114 | | inpn_metropole | znieff1_mer | 115 | | inpn_metropole | znieff2 | 116 | | inpn_metropole | znieff2_mer | 117 | | inpn_metropole | zones_de_protection_speciale | 118 | 119 | 120 | * **Lire les données** des couches WFS via une **simple requête** sur les tables étrangères: 121 | 122 | ```sql 123 | -- Tester 124 | SELECT * 125 | FROM inpn_metropole.zico 126 | LIMIT 1; 127 | ``` 128 | 129 | **Attention**, lorsqu'on accède depuis PostgreSQL à un serveur WFS, on est tributaire 130 | 131 | * des performances de ce serveur, 132 | * et du temps de transfert des données vers la base. 133 | 134 | Nous **déconseillons fortement** dans ce cas de charger le serveur externe en réalisant des requêtes complexes (ou trop fréquentes) sur ces tables étrangères, surtout lorsque les données évoluent peu. 135 | 136 | Au contraire, nous conseillons de créer des **vues matérialisées** à partir des tables étrangères pour éviter des requêtes lourdes en stockant les données dans la base: 137 | 138 | ```sql 139 | -- Pour éviter de requêter à chaque fois le WFS, on peut créer des vues matérialisées 140 | 141 | -- suppression de la vue si elle existe déjà 142 | DROP MATERIALIZED VIEW IF EXISTS inpn_metropole.vm_zico; 143 | 144 | -- création de la vue: on doit parfois forcer le type de géométrie attendue 145 | CREATE MATERIALIZED VIEW inpn_metropole.vm_zico AS 146 | SELECT *, 147 | (ST_multi(msgeometry))::geometry(multipolygon, 2154) AS geom 148 | FROM inpn_metropole.zico 149 | ; 150 | 151 | -- Ajout d'un index spatial sur la géométrie 152 | CREATE INDEX ON inpn_metropole.vm_zico USING GIST (geom); 153 | ``` 154 | 155 | Une fois la vue créée, vous pouvez faire vos requêtes sur cette vue, avec des performances bien meilleures et un allègement de la charge sur le serveur externe. 156 | 157 | Pour **rafraîchir** les données à partir du serveur WFS, il suffit de rafraîchir la ou les vues matérialisées: 158 | 159 | ```sql 160 | -- Rafraîchir la vue, par exemple à lancer une fois par mois 161 | REFRESH MATERIALIZED VIEW inpn_metropole.vm_zico; 162 | ``` 163 | 164 | ## Le FDW postgres_fdw pour accéder aux tables d'une autre base de données PostgreSQL 165 | 166 | ```sql 167 | -- Création du serveur externe 168 | DROP SERVER IF EXISTS foreign_server_test CASCADE; 169 | CREATE SERVER IF NOT EXISTS foreign_server_test 170 | FOREIGN DATA WRAPPER postgres_fdw 171 | OPTIONS (host 'mon_serveur_postgresql_externe.com', port '5432', dbname 'external_database') 172 | ; 173 | 174 | -- on déclare se connecter en tant qu'utilisateur `mon_utilisateur_externe` lorsqu'on récupère des données 175 | -- depuis une connexion avec l'utilisateur interne `mon_utilisateur` 176 | CREATE USER MAPPING FOR "mon_utilisateur" 177 | SERVER foreign_server_test 178 | OPTIONS (user 'mon_utilisateur_externe', password '***********'); 179 | 180 | -- on stocke les tables étrangères dans un schéma spécifique pour isoler des autres schémas en dur 181 | DROP SCHEMA IF EXISTS fdw_test_schema CASCADE; 182 | CREATE SCHEMA IF NOT EXISTS fdw_test_schema; 183 | 184 | -- importer automatiquement les tables d'un schéma de la base distante 185 | IMPORT FOREIGN SCHEMA "un_schema" 186 | LIMIT TO ("une_table", "une_autre_table") 187 | FROM SERVER foreign_server_test 188 | INTO fdw_test_schema; 189 | 190 | -- Tester 191 | SELECT * FROM fdw_test_schema.une_table LIMIT 1; 192 | ``` 193 | 194 | 195 | Continuer vers [Tutoriels en ligne](./tutoriel.md) 196 | -------------------------------------------------------------------------------- /docs/filter_data.md: -------------------------------------------------------------------------------- 1 | # Filtrer les données : la clause WHERE 2 | 3 | Récupérer les données à partir de la **valeur exacte d'un champ**. Ici le nom de la commune 4 | 5 | ```sql 6 | -- Récupérer seulement la commune du Havre 7 | SELECT id_commune, code_insee, nom, 8 | population 9 | FROM z_formation.commune 10 | WHERE nom = 'Le Havre' 11 | ``` 12 | 13 | On peut chercher les lignes dont le champ correspondant à **plusieurs valeurs** 14 | 15 | ```sql 16 | -- Récupérer la commune du Havre et de Rouen 17 | SELECT id_commune, code_insee, nom, 18 | population 19 | FROM z_formation.commune 20 | WHERE nom IN ('Le Havre', 'Rouen') 21 | ``` 22 | 23 | On peut aussi filtrer sur des champs de type **entier ou nombres réels**, et faire des conditions comme des inégalités. 24 | 25 | ```sql 26 | -- Filtrer les données, par exemple par département et population 27 | SELECT * 28 | FROM z_formation.commune 29 | WHERE True 30 | AND depart = 'SEINE-MARITIME' 31 | AND population > 1000 32 | ; 33 | ``` 34 | 35 | On peut chercher des lignes dont un champ **commence et/ou se termine** par un texte 36 | 37 | ```sql 38 | -- Filtrer les données, par exemple par département et début et/ou fin de nom 39 | SELECT * 40 | FROM z_formation.commune 41 | WHERE True 42 | AND depart = 'SEINE-MARITIME' 43 | -- commence par C 44 | AND nom LIKE 'C%' 45 | -- se termine par ville 46 | AND nom ILIKE '%ville' 47 | ; 48 | ``` 49 | 50 | On peut utiliser les **calculs sur les géométries** pour filtrer les données. Par exemple filtrer par longueur de lignes 51 | 52 | ```sql 53 | -- Les routes qui font plus que 10km 54 | -- on peut utiliser des fonctions dans la clause WHERE 55 | SELECT id_route, id, geom 56 | FROM z_formation.route 57 | WHERE True 58 | AND ST_Length(geom) > 10000 59 | ``` 60 | 61 | Continuer vers [Regrouper des données: GROUP BY](./group_data.md) 62 | 63 | ## Quiz 64 |
65 | Écrire une requête retournant toutes les communes de Seine-Maritime qui contiennent la chaîne de caractères 'saint' 66 | 67 | ```sql 68 | -- Toutes les communes de Seine-Maritime qui contiennent le mot saint 69 | SELECT * 70 | FROM z_formation.commune 71 | WHERE True 72 | AND depart = 'SEINE-MARITIME' 73 | AND nom ILIKE '%saint%'; 74 | ``` 75 |
76 | 77 |
78 | Écrire une requête retournant les nom et centroïde des communes de Seine-Maritime avec une population inférieure ou égale à 50 79 | 80 | ```sql 81 | -- Nom et centroïde des communes de Seine-Maritime avec une population <= 50 82 | SELECT nom, ST_Centroid(geom) as geom 83 | FROM z_formation.commune 84 | WHERE True 85 | AND depart = 'SEINE-MARITIME' 86 | AND population <= 50 87 | ``` 88 |
89 | -------------------------------------------------------------------------------- /docs/grant.md: -------------------------------------------------------------------------------- 1 | # Gestion des droits 2 | 3 | Dans PostgreSQL, on peut créer des rôles (des utilisateurs) et gérer les droits sur les différents objets : 4 | base, schémas, tables, fonctions, etc. 5 | 6 | La [documentation officielle de PostgreSQL](https://www.postgresql.org/docs/current/sql-grant.html) est complète, et propose plusieurs exemples. 7 | 8 | Nous montrons ci-dessous quelques utilisations possibles. 9 | Attention, pour pouvoir réaliser certaines opérations, vous devez : 10 | 11 | * soit être **super-utilisateur** (créer un rôle de connexion) 12 | * soit être **propriétaire** des objets pour lesquels modifier les droits (schémas, tables) 13 | 14 | ## Donner ou retirer des droits sur des objets existants 15 | 16 | Création d'un schéma de test et d'un rôle de connexion, en tant qu'utilisateur avec des droits forts sur la base de données (création de schémas, de tables, etc.). 17 | 18 | ```sql 19 | -- création d'un schéma de test 20 | CREATE SCHEMA IF NOT EXISTS nouveau_schema; 21 | 22 | -- création de tables pour tester 23 | CREATE TABLE IF NOT EXISTS nouveau_schema.observation (id serial primary key, nom text, geom geometry(point, 2154)); 24 | CREATE TABLE IF NOT EXISTS nouveau_schema.nomenclature (id serial primary key, code text, libelle text); 25 | ``` 26 | 27 | Création d'un rôle de connexion (en tant que super-utilisateur, ou en tant qu'utilisateur ayant le droit de créer des rôles) 28 | 29 | ```sql 30 | -- création d'un rôle nommé invite 31 | CREATE ROLE invite WITH PASSWORD 'mot_de_passe_a_changer' LOGIN; 32 | ``` 33 | 34 | On donne le droit de connexion sur la base (nommée ici qgis) 35 | 36 | ```sql 37 | -- on donne le droit de connexion sur la base 38 | GRANT CONNECT ON DATABASE qgis TO invite; 39 | ``` 40 | 41 | Exemple de requêtes pratiques pour donner ou retirer des droits (en tant qu'utilisateur propriétaire de la base et des objets) 42 | 43 | ```sql 44 | -- on donne le droit à invite d'utiliser les schéma public et nouveau_schema 45 | -- Utile pour pouvoir lister les tables 46 | -- Si un rôle n'a pas le droit USAGE sur un schéma, 47 | -- il ne peut pas lire les données des tables 48 | -- même si des droits SELECT on été données sur ces tables 49 | GRANT USAGE ON SCHEMA public, nouveau_schema TO "invite", "autre_role"; 50 | 51 | -- on permet à invite de lire les données (SELECT) 52 | -- de toutes les tables du schéma nouveau_schema 53 | GRANT SELECT ON ALL TABLES IN SCHEMA nouveau_schema TO "invite", "autre_role"; 54 | 55 | -- On permet l'ajout et la modification de données sur la table observation seulement 56 | GRANT INSERT OR UPDATE ON TABLE nouveau_schema.observation TO "invite"; 57 | 58 | -- On peut aussi enlever des droits avec REVOKE. 59 | -- Cela enlève seulement les droits donnés précédemment avec GRANT 60 | -- Ex: On pourrait donner tous les droits sur une table 61 | -- puis retirer la possibilité de faire des suppressions 62 | GRANT ALL ON TABLE nouveau_schema.observation TO "autre_role"; 63 | -- on retire les droits DELETE et TRUNCATE 64 | REVOKE DELETE, TRUNCATE ON TABLE nouveau_schema.observation FROM "autre_role"; 65 | 66 | -- On peut aussi par exemple retirer tous les privilèges sur les tables du schéma public 67 | REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM "invite"; 68 | 69 | ``` 70 | 71 | ## Droits par défaut sur les nouveaux objets créés par un utilisateur. 72 | 73 | Lorsqu'un utilisateur crée un schéma, une table ou une vue, aucun droit n'est donné 74 | sur cet objet aux autres utilisateurs. Par défaut les autres utilisateurs ne peuvent 75 | donc pas par exemple lire les données de ce nouvel objet. 76 | 77 | PostgreSQL fournit un moyen de définir en quelque sorte: 78 | *Donner ce(s) droit(s) sur tous ces objets créés par cet utilisateur à ces autres utilisateurs* 79 | 80 | Documentation officielle : https://docs.postgresql.fr/current/sql-alterdefaultprivileges.html 81 | 82 | ```sql 83 | -- Donner le droit SELECT pour toutes les nouvelles tables créées à l'avenir 84 | -- dans le schéma nouveau_schema 85 | ALTER DEFAULT PRIVILEGES IN SCHEMA "nouveau_schema" GRANT SELECT ON TABLES TO "invite", "autre_role"; 86 | ``` 87 | 88 | ## Lister tous les droits donnés sur tous les objets de la base 89 | 90 | Une requête SQL peut être utilisée pour lister tous les droits accordés 91 | sur plusieurs types d'objets : schéma, tables, fonctions, types, aggrégats, etc. 92 | 93 | Un exemple de **résultat** : 94 | 95 | object_schema | object_type | object_name | object_owner | grantor | grantee | privileges | is_grantable | 96 | ---------------|-------------|---------------|---------------|----------|--------------|-------------------------|---------------| 97 | urbanisme | schema | urbanisme | role_sig | role_sig | role_urba | CREATE, USAGE | f | 98 | urbanisme | table | zone_urba | role_sig | role_sig | role_urba | INSERT, SELECT, UPDATE | f | 99 | cadastre | schema | cadastre | role_sig | role_sig | role_lecteur | USAGE | f | 100 | cadastre | table | commune | role_sig | role_sig | role_lecteur | SELECT | f | 101 | cadastre | table | parcelle | role_sig | role_sig | role_lecteur | SELECT | f | 102 | 103 | > Si un objet n'est pas retourné par cette requête, 104 | c'est qu'aucun droit spécifique ne lui a été accordé. 105 | 106 |
107 | 108 | Requête SQL permettant de récupérer les droits accordés 109 | sur tous les objets de la base, ainsi que les propriétaires 110 | et les rôles qui ont accordé ces privilèges 111 | 112 | 113 | ```sql 114 | -- Adapted from https://dba.stackexchange.com/a/285632 115 | WITH rol AS ( 116 | SELECT oid, 117 | rolname::text AS role_name 118 | FROM pg_roles 119 | UNION 120 | SELECT 0::oid AS oid, 121 | 'public'::text 122 | ), 123 | schemas AS ( -- Schemas 124 | SELECT oid AS schema_oid, 125 | n.nspname::text AS schema_name, 126 | n.nspowner AS owner_oid, 127 | 'schema'::text AS object_type, 128 | coalesce ( n.nspacl, acldefault ( 'n'::"char", n.nspowner ) ) AS acl 129 | FROM pg_catalog.pg_namespace n 130 | WHERE n.nspname !~ '^pg_' 131 | AND n.nspname <> 'information_schema' 132 | ), 133 | classes AS ( -- Tables, views, etc. 134 | SELECT schemas.schema_oid, 135 | schemas.schema_name AS object_schema, 136 | c.oid, 137 | c.relname::text AS object_name, 138 | c.relowner AS owner_oid, 139 | CASE 140 | WHEN c.relkind = 'r' THEN 'table' 141 | WHEN c.relkind = 'v' THEN 'view' 142 | WHEN c.relkind = 'm' THEN 'materialized view' 143 | WHEN c.relkind = 'c' THEN 'type' 144 | WHEN c.relkind = 'i' THEN 'index' 145 | WHEN c.relkind = 'S' THEN 'sequence' 146 | WHEN c.relkind = 's' THEN 'special' 147 | WHEN c.relkind = 't' THEN 'TOAST table' 148 | WHEN c.relkind = 'f' THEN 'foreign table' 149 | WHEN c.relkind = 'p' THEN 'partitioned table' 150 | WHEN c.relkind = 'I' THEN 'partitioned index' 151 | ELSE c.relkind::text 152 | END AS object_type, 153 | CASE 154 | WHEN c.relkind = 'S' THEN coalesce ( c.relacl, acldefault ( 's'::"char", c.relowner ) ) 155 | ELSE coalesce ( c.relacl, acldefault ( 'r'::"char", c.relowner ) ) 156 | END AS acl 157 | FROM pg_class c 158 | JOIN schemas 159 | ON ( schemas.schema_oid = c.relnamespace ) 160 | WHERE c.relkind IN ( 'r', 'v', 'm', 'S', 'f', 'p' ) 161 | ), 162 | cols AS ( -- Columns 163 | SELECT c.object_schema, 164 | null::integer AS oid, 165 | c.object_name || '.' || a.attname::text AS object_name, 166 | 'column' AS object_type, 167 | c.owner_oid, 168 | coalesce ( a.attacl, acldefault ( 'c'::"char", c.owner_oid ) ) AS acl 169 | FROM pg_attribute a 170 | JOIN classes c 171 | ON ( a.attrelid = c.oid ) 172 | WHERE a.attnum > 0 173 | AND NOT a.attisdropped 174 | ), 175 | procs AS ( -- Procedures and functions 176 | SELECT schemas.schema_oid, 177 | schemas.schema_name AS object_schema, 178 | p.oid, 179 | p.proname::text AS object_name, 180 | p.proowner AS owner_oid, 181 | CASE p.prokind 182 | WHEN 'a' THEN 'aggregate' 183 | WHEN 'w' THEN 'window' 184 | WHEN 'p' THEN 'procedure' 185 | ELSE 'function' 186 | END AS object_type, 187 | pg_catalog.pg_get_function_arguments ( p.oid ) AS calling_arguments, 188 | coalesce ( p.proacl, acldefault ( 'f'::"char", p.proowner ) ) AS acl 189 | FROM pg_proc p 190 | JOIN schemas 191 | ON ( schemas.schema_oid = p.pronamespace ) 192 | ), 193 | udts AS ( -- User defined types 194 | SELECT schemas.schema_oid, 195 | schemas.schema_name AS object_schema, 196 | t.oid, 197 | t.typname::text AS object_name, 198 | t.typowner AS owner_oid, 199 | CASE t.typtype 200 | WHEN 'b' THEN 'base type' 201 | WHEN 'c' THEN 'composite type' 202 | WHEN 'd' THEN 'domain' 203 | WHEN 'e' THEN 'enum type' 204 | WHEN 't' THEN 'pseudo-type' 205 | WHEN 'r' THEN 'range type' 206 | WHEN 'm' THEN 'multirange' 207 | ELSE t.typtype::text 208 | END AS object_type, 209 | coalesce ( t.typacl, acldefault ( 'T'::"char", t.typowner ) ) AS acl 210 | FROM pg_type t 211 | JOIN schemas 212 | ON ( schemas.schema_oid = t.typnamespace ) 213 | WHERE ( t.typrelid = 0 214 | OR ( SELECT c.relkind = 'c' 215 | FROM pg_catalog.pg_class c 216 | WHERE c.oid = t.typrelid ) ) 217 | AND NOT EXISTS ( 218 | SELECT 1 219 | FROM pg_catalog.pg_type el 220 | WHERE el.oid = t.typelem 221 | AND el.typarray = t.oid ) 222 | ), 223 | fdws AS ( -- Foreign data wrappers 224 | SELECT null::oid AS schema_oid, 225 | null::text AS object_schema, 226 | p.oid, 227 | p.fdwname::text AS object_name, 228 | p.fdwowner AS owner_oid, 229 | 'foreign data wrapper' AS object_type, 230 | coalesce ( p.fdwacl, acldefault ( 'F'::"char", p.fdwowner ) ) AS acl 231 | FROM pg_foreign_data_wrapper p 232 | ), 233 | fsrvs AS ( -- Foreign servers 234 | SELECT null::oid AS schema_oid, 235 | null::text AS object_schema, 236 | p.oid, 237 | p.srvname::text AS object_name, 238 | p.srvowner AS owner_oid, 239 | 'foreign server' AS object_type, 240 | coalesce ( p.srvacl, acldefault ( 'S'::"char", p.srvowner ) ) AS acl 241 | FROM pg_foreign_server p 242 | ), 243 | all_objects AS ( 244 | SELECT schema_name AS object_schema, 245 | object_type, 246 | schema_name AS object_name, 247 | null::text AS calling_arguments, 248 | owner_oid, 249 | acl 250 | FROM schemas 251 | UNION 252 | SELECT object_schema, 253 | object_type, 254 | object_name, 255 | null::text AS calling_arguments, 256 | owner_oid, 257 | acl 258 | FROM classes 259 | UNION 260 | SELECT object_schema, 261 | object_type, 262 | object_name, 263 | null::text AS calling_arguments, 264 | owner_oid, 265 | acl 266 | FROM cols 267 | UNION 268 | SELECT object_schema, 269 | object_type, 270 | object_name, 271 | calling_arguments, 272 | owner_oid, 273 | acl 274 | FROM procs 275 | UNION 276 | SELECT object_schema, 277 | object_type, 278 | object_name, 279 | null::text AS calling_arguments, 280 | owner_oid, 281 | acl 282 | FROM udts 283 | UNION 284 | SELECT object_schema, 285 | object_type, 286 | object_name, 287 | null::text AS calling_arguments, 288 | owner_oid, 289 | acl 290 | FROM fdws 291 | UNION 292 | SELECT object_schema, 293 | object_type, 294 | object_name, 295 | null::text AS calling_arguments, 296 | owner_oid, 297 | acl 298 | FROM fsrvs 299 | ), 300 | acl_base AS ( 301 | SELECT object_schema, 302 | object_type, 303 | object_name, 304 | calling_arguments, 305 | owner_oid, 306 | ( aclexplode ( acl ) ).grantor AS grantor_oid, 307 | ( aclexplode ( acl ) ).grantee AS grantee_oid, 308 | ( aclexplode ( acl ) ).privilege_type AS privilege_type, 309 | ( aclexplode ( acl ) ).is_grantable AS is_grantable 310 | FROM all_objects 311 | ), 312 | ungrouped AS ( 313 | SELECT acl_base.object_schema, 314 | acl_base.object_type, 315 | acl_base.object_name, 316 | --acl_base.calling_arguments, 317 | owner.role_name AS object_owner, 318 | grantor.role_name AS grantor, 319 | grantee.role_name AS grantee, 320 | acl_base.privilege_type, 321 | acl_base.is_grantable 322 | FROM acl_base 323 | JOIN rol owner 324 | ON ( owner.oid = acl_base.owner_oid ) 325 | JOIN rol grantor 326 | ON ( grantor.oid = acl_base.grantor_oid ) 327 | JOIN rol grantee 328 | ON ( grantee.oid = acl_base.grantee_oid ) 329 | WHERE acl_base.grantor_oid <> acl_base.grantee_oid 330 | ) 331 | SELECT 332 | object_schema, object_type, object_name, object_owner, 333 | grantor, grantee, 334 | -- The same function name can be used many times 335 | -- Since we do not include the calling_arguments field, we should add a DISTINCT below 336 | string_agg(DISTINCT privilege_type, ' - ' ORDER BY privilege_type) AS privileges, 337 | is_grantable 338 | FROM ungrouped 339 | WHERE True 340 | -- Simplify objects returned 341 | -- You can comment the following line to get these types too 342 | AND object_type NOT IN ('function', 'window', 'aggregate', 'base type', 'composite type') 343 | -- You can also filter for specific schemas or object names by uncommenting and adapting the following lines 344 | -- AND object_schema IN ('cadastre', 'environment') 345 | -- AND object_type = 'table' 346 | -- AND object_name ILIKE '%parcelle%' 347 | GROUP BY object_schema, object_type, object_name, object_owner, grantor, grantee, is_grantable 348 | ORDER BY object_schema, object_type, grantor, grantee, object_name 349 | ; 350 | ``` 351 | 352 |
353 | 354 | 355 | Continuer vers [Accéder à des données externes: Foreign Data Wrapper](./fdw.md) 356 | -------------------------------------------------------------------------------- /docs/group_data.md: -------------------------------------------------------------------------------- 1 | # Grouper des données et calculer des statistiques 2 | 3 | [Les fonctions d'agrégat dans PostgreSQL](https://docs.postgresql.fr/14/functions-aggregate.html) 4 | 5 | ## Valeurs distinctes d'un champ 6 | 7 | On souhaite récupérer **toutes les valeurs possibles** d'un champ 8 | 9 | ```sql 10 | -- Vérifier les valeurs distinctes d'un champ: table commune 11 | SELECT DISTINCT depart 12 | FROM z_formation.commune 13 | ORDER BY depart 14 | 15 | -- idem sur la table lieu_dit_habite 16 | SELECT DISTINCT nature 17 | FROM z_formation.lieu_dit_habite 18 | ORDER BY nature 19 | ``` 20 | 21 | 22 | ## Regrouper des données en spécifiant les champs de regroupement 23 | 24 | Certains calculs nécessitent le regroupement de lignes, comme les moyennes, les sommes ou les totaux. Pour cela, il faut réaliser un **regroupement** via la clause `GROUP BY` 25 | 26 | **Compter** les communes par département et calculer la **population totale** 27 | 28 | ```sql 29 | -- Regrouper des données 30 | -- Compter le nombre de communes par département 31 | SELECT depart, 32 | count(code_insee) AS nb_commune, 33 | sum(population) AS total_population 34 | FROM z_formation.commune 35 | WHERE True 36 | GROUP BY depart 37 | ORDER BY nb_commune DESC 38 | ``` 39 | 40 | Calculer des **statistiques sur l'aire** des communes pour chaque département 41 | 42 | 43 | ```sql 44 | SELECT depart, 45 | count(id_commune) AS nb, 46 | min(ST_Area(geom)/10000)::int AS min_aire_ha, 47 | max(ST_Area(geom)/10000)::int AS max_aire_ha, 48 | avg(ST_Area(geom)/10000)::int AS moy_aire_ha, 49 | sum(ST_Area(geom)/10000)::int AS total_aire_ha 50 | FROM z_formation.commune 51 | GROUP BY depart 52 | ``` 53 | 54 | **Compter** le nombre de routes par nature 55 | 56 | ```sql 57 | -- Compter le nombre de routes par nature 58 | SELECT count(id_route) AS nb_route, nature 59 | FROM z_formation.route 60 | WHERE True 61 | GROUP BY nature 62 | ORDER BY nb_route DESC 63 | ``` 64 | 65 | Compter le nombre de routes par nature et par sens 66 | 67 | ```sql 68 | SELECT count(id_route) AS nb_route, nature, sens 69 | FROM z_formation.route 70 | WHERE True 71 | GROUP BY nature, sens 72 | ORDER BY nature, sens DESC 73 | ``` 74 | 75 | Les caculs sur des ensembles groupés peuvent aussi être réalisé **sur les géométries.**. Les plus utilisés sont 76 | 77 | * `ST_Collect` qui regroupe les géométries dans une multi-géométrie, 78 | * `ST_Union` qui fusionne les géométries. 79 | 80 | Par exemple, on peut souhaiter trouver l'**enveloppe convexe** autour de points (élastique tendu autour d'un groupe de points). Ici, nous regroupons les lieux-dits par nature (ce qui n'a pas beaucoup de sens, mais c'est pour l'exemple). Dans ce cas, il faut faire une sous-requête pour filtrer seulement les résultats de type polygone (car s'il y a seulement 1 ou 2 objets par nature, alors on ne peut créer de polygone) 81 | 82 | 83 | ```sql 84 | SELECT * 85 | FROM ( 86 | SELECT 87 | nature, 88 | -- ST_Convexhull renvoie l'enveloppe convexe 89 | ST_Convexhull(ST_Collect(geom)) AS geom 90 | FROM z_formation.lieu_dit_habite 91 | GROUP BY nature 92 | ) AS source 93 | -- GeometryType renvoie le type de géométrie 94 | WHERE Geometrytype(geom) = 'POLYGON' 95 | ``` 96 | 97 | Attention, on doit donner un alias à la sous-requête (ici `source`) 98 | 99 | 100 | Un autre exemple sur les bornes. Ici, on groupe les bornes par identifiant pair ou impair, et on calcule l'enveloppe convexe 101 | 102 | ```sql 103 | SELECT count(id_borne), ((id_borne % 2) = 0) AS pair, 104 | (st_convexhull(ST_Collect(geom))) AS geom 105 | FROM z_formation.borne_incendie 106 | GROUP BY pair 107 | ``` 108 | 109 | 110 | On peut réaliser l'équivalent d'un `DISSOLVE` de QGIS en regroupant les géométries via `ST_Union`. Par exemple fusionner l'ensemble des communes pour construire les géométries des départements: 111 | 112 | ```sql 113 | SELECT 114 | depart, 115 | count(id_commune) AS nb_com, 116 | -- ST_Union crée une seule géométrie en fusionnant les géométries. 117 | ST_Union(geom) AS geom 118 | 119 | FROM z_formation.commune 120 | 121 | GROUP BY depart 122 | ``` 123 | 124 | Attention, cette requête est lourde, et devra être enregistrée comme une table. 125 | 126 | ## Filtrer sur les regroupements 127 | 128 | Si on souhaite **compter** les communes par département, calculer la **population totale** et aussi **filter celles qui ont plus de 500 000 habitants**, il peut paraître logique d'écrire cette requête : 129 | 130 | ```sql 131 | SELECT depart, 132 | count(code_insee) AS nb_commune, 133 | sum(population) AS total_population 134 | FROM z_formation.commune 135 | GROUP BY depart 136 | WHERE sum(population) > 500000 137 | ORDER BY nb_commune DESC 138 | ``` 139 | 140 | ou bien encore : 141 | 142 | ```sql 143 | SELECT depart, 144 | count(code_insee) AS nb_commune, 145 | sum(population) AS total_population 146 | FROM z_formation.commune 147 | GROUP BY depart 148 | WHERE total_population > 500000 149 | ORDER BY nb_commune DESC 150 | ``` 151 | 152 | Ces deux requêtes renvoient une erreur. La bonne requête est : 153 | 154 | ```sql 155 | SELECT depart, 156 | count(code_insee) AS nb_commune, 157 | sum(population) AS total_population 158 | FROM z_formation.commune 159 | GROUP BY depart 160 | HAVING sum(population) > 500000 161 | ORDER BY nb_commune DESC 162 | ``` 163 | 164 | Il faut savoir que la clause `WHERE` est exécutée avant la clause `GROUP BY`, il n'est donc pas possible de filtrer sur des regroupements avec celle-ci. C'est le rôle de la clause `HAVING`. 165 | 166 | Aussi la clause `SELECT` est exécutée après les clauses `WHERE` et `HAVING`, il n'est donc pas possible d'utiliser des alias déclarés avec celle-ci. 167 | 168 | Un schéma illustrant ceci est disponible sur le site [postgresqltutorial.com](https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-having/). 169 | 170 | Continuer vers [Rassembler des données: UNION ALL](./union.md) 171 | 172 | ## Quiz 173 | 174 |
175 | Écrire une requête retournant, pour le/les département(s) dont la population moyenne des villes est supérieure ou égale à 1500 habitants, le nom du/des département(s) ainsi que cette moyenne. 176 | 177 | ```sql 178 | SELECT depart, 179 | avg(population) AS moyenne_population 180 | FROM z_formation.commune 181 | GROUP BY depart 182 | HAVING avg(population) >= 1500 183 | ``` 184 |
185 | 186 |
187 | Écrire une requête retournant pour les départements 'SEINE-MARITIME' et 'EURE', leur nom, le nombre de communes ainsi que la surface et la surface de l'enveloppe convexe en mètre carré sous forme d'entier. 188 | 189 | ```sql 190 | SELECT depart, 191 | count(id_commune) AS nb_commune, 192 | ST_Area(ST_Collect(geom))::int8 AS surface, 193 | ST_Area(ST_Convexhull(ST_Collect(geom)))::int8 AS surface_enveloppe_convexe 194 | FROM z_formation.commune 195 | WHERE depart IN ('SEINE-MARITIME', 'EURE') 196 | GROUP BY depart 197 | ``` 198 |
199 | -------------------------------------------------------------------------------- /docs/import_data.md: -------------------------------------------------------------------------------- 1 | # Importer des données 2 | 3 | Pour la formation, on doit **importer des données** pour pouvoir travailler. 4 | 5 | ## Import d'une couche depuis QGIS 6 | 7 | On doit **charger au préalable la couche source** dans QGIS (SHP, TAB, etc.), puis on doit **vérifier** : 8 | 9 | * la **projection**, idéalement `EPSG:2154` 10 | * l'**encodage** : `UTF-8`, `ISO-8859-15`, etc. Il faut ouvrir la **table attributaire**, et vérifier si les accents sont bien affichés. Sinon choisir le bon encodage dans l'onglet **Général** des **propriétés de la couche** 11 | * les **champs**: noms, type, contenu 12 | 13 | Pour importer, il existe plusieurs manières dans QGIS. La plus **performante** pour des gros volumes de données est l'utilisation de l'algorithme de la `boîte à outils` du menu `Traitement` appelé `Exporter vers PostgreSQL (Connexions disponibles`. 14 | 15 | ![Exporter vers PostgreSQL](media/qgis_traitement_exporter_postgresql_ogr.png) 16 | 17 | Pour trouver cet algorithme, chercher `PosgreSQL` dans le champ du haut, et lancer l'algorithme **Exporter vers PostgreSQL (connexions disponibles)** de **GDAL**. Il faut choisir les options suivantes : 18 | 19 | * choisir la bonne **connexion**, la couche en entrée, etc. 20 | * choisir le **schéma**, par exemple `z_formation` 21 | * choisir le **nom de la table**, par exemple `commune` 22 | * laisser `id` dans le champ **Clef primaire** si aucun champ entier auto-incrémenté existe, ou choisir le champ approprié 23 | * décocher **Convertir en morceaux multiples** pour les couches de points (et aussi pour les lignes et polygones si on est sûr) 24 | * laisser le reste par défaut. 25 | 26 | ![Algorithe d'export vers PostgreSQL](media/qgis_traitement_exporter_dialogue_algorithme.png) 27 | 28 | Après l'import, on peut charger la table comme une couche via **l'explorateur de QGIS** : 29 | 30 | * **rafraîchir** le contenu du schéma via clic-droit et `Rafraîchir` 31 | * **double-cliquer** sur la table 32 | 33 | ## Importer plusieurs couches en batch 34 | 35 | Il est possible d'utiliser l'outil **Importer un vecteur vers une base de données PostGIS (connexions disponibles)** par lot. Pour cela, une fois la boîte de dialogue de cet algorithme ouverte, cliquer sur le bouton **Exécuter comme processus de lot**. Cela affiche un tableau, ou chaque ligne représente les variables d'entrée d'un algorithme. 36 | 37 | Vous pouvez créer manuellement chaque ligne, ou choisir directement les couches depuis votre projet QGIS. Voir la documentation QGIS pour plus de détail: 38 | https://docs.qgis.org/latest/fr/docs/user_manual/processing/batch.html 39 | 40 | 41 | Continuer vers [Sélectionner des données : SELECT](./sql_select.md) 42 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: PostGIS 3 | Favicon: logo.svg 4 | ... 5 | 6 | # Formation PostGIS 7 | 8 | ## Pré-requis 9 | 10 | Cette formation concerne des utilisateurs de QGIS, géomaticiens, qui souhaitent comprendre l'apport de l'utilisation de PostgreSQL comme outil de centralisation de la données spatiale (et non spatiale): 11 | 12 | * un lieu unique de stockage 13 | * une gestion des droits d'accès (lecture, écriture) 14 | * la reproduction de quasiment tous les besoins en traitements SIG : intersections, tampons, extraction, correction, etc. 15 | * une grande souplesse de manipulation des données 16 | * des performances élevés sur certains traitements spatiaux (et non spatiaux) 17 | * le stockage de fonctions et de triggers pour assurer la cohérence des données, stocker des outils directement dans la base 18 | 19 | 20 | ## Sommaire 21 | 22 | * [Liens utiles et jeu de données](./links_and_data.md) 23 | * [Gestion des données PostgreSQL dans QGIS](./postgresql_in_qgis.md) 24 | * [Import des données dans PostgreSQL](./import_data.md) 25 | * [Sélectionner des données: SELECT](./sql_select.md) 26 | * [Réaliser des calculs et créer des géométries: FONCTIONS](./perform_calculation.md) 27 | * [Filtrer des données: WHERE](./filter_data.md) 28 | * [Regrouper des données: GROUP BY](./group_data.md) 29 | * [Rassembler des données: UNION ALL](./union.md) 30 | * [Enregistrer les requêtes: VIEW](./save_queries.md) 31 | * [Réaliser des jointures attributaires et spatiales; JOIN](./join_data.md) 32 | * [Fusionner des géométries](./merge_geometries.md) 33 | * [Les triggers](./triggers.md) 34 | * [Correction des géométries invalides](./validate_geometries.md) 35 | * [Vérifier la topologie](./check_topology.md) 36 | * [Fonctions utiles](./utils.md) 37 | * [Gestion des droits](./grant.md) 38 | * [Accéder à des données externes: Foreign Data Wrapper](./fdw.md) 39 | * [Tutoriels en ligne](./tutoriel.md) 40 | -------------------------------------------------------------------------------- /docs/join_data.md: -------------------------------------------------------------------------------- 1 | # Les jointures 2 | 3 | Les jointures permettent de récupérer des données en relation les unes par rapport aux autres. 4 | 5 | ## Les jointures attributaires 6 | 7 | La condition de jointure est faite sur des champs non géométriques. Par exemple une égalité (code, identifiant). 8 | 9 | ### Exemple 1: parcelles et communes 10 | 11 | Récupération des informations de la commune pour un ensemble de parcelles 12 | 13 | ```sql 14 | -- Jointure attributaire: récupération du nom de la commune pour un ensemble de parcelles 15 | SELECT c.nom, p.* 16 | FROM z_formation.parcelle as p 17 | JOIN z_formation.commune as c 18 | ON p.commune = c.code_insee 19 | LIMIT 100 20 | -- IMPORTANT: ne pas oublier le ON cad le critère de jointure, 21 | -- sous peine de "produit cartésien" (calcul coûteux de tous les possibles) 22 | ; 23 | ``` 24 | 25 | Il est souvent intéressant, pour des données volumineuses, de **créer un index sur le champ de jointure** (par exemple ici sur les champs `commune` et `code_insee`. 26 | 27 | 28 | ### Exemple 2: observations et communes 29 | 30 | * On crée une table de points qui contiendra des observations 31 | 32 | ```sql 33 | -- création 34 | CREATE TABLE z_formation.observation ( 35 | id serial NOT NULL PRIMARY KEY, 36 | date date DEFAULT (now())::date NOT NULL, 37 | description text, 38 | geom public.geometry(Point,2154), 39 | code_insee character varying(5) 40 | ); 41 | CREATE INDEX sidx_observation_geom ON z_formation.observation USING gist (geom); 42 | 43 | -- on y met des données 44 | INSERT INTO z_formation.observation VALUES (1, '2020-07-08', 'un', '01010000206A080000D636D95AFB832141279BD2C8FEA65A41', '76618'); 45 | INSERT INTO z_formation.observation VALUES (2, '2020-07-08', 'deux', '01010000206A08000010248E173E37224156920AEA21525A41', '27213'); 46 | INSERT INTO z_formation.observation VALUES (3, '2020-07-08', 'trois', '01010000206A08000018BF3048EA112341183933F6CC885A41', NULL); 47 | 48 | ``` 49 | 50 | On fait une jointure attributaire entre les points des observations et les communes 51 | 52 | ```sql 53 | SELECT 54 | -- tous les champs de la table observation 55 | o.*, 56 | -- le nom de la commune 57 | c.nom, 58 | -- l'aire entière en hectares 59 | ST_area(c.geom)::integer/10000 AS surface_commune 60 | FROM z_formation.observation AS o 61 | JOIN z_formation.commune AS c ON o.code_insee = c.code_insee 62 | WHERE True 63 | ``` 64 | 65 | Résultat: 66 | 67 | | id | date | description | geom | code_insee | nom | surface_commune | 68 | |----|------------|-------------|------|------------|----------------|-----------------| 69 | | 2 | 2020-07-08 | deux | .... | 27213 | Vexin-sur-Epte | 11434 | 70 | | 1 | 2020-07-08 | un | .... | 76618 | Petit-Caux | 9243 | 71 | 72 | On ne récupère ici que 2 lignes alors qu'il y a bien 3 observations dans la table. 73 | 74 | Pour récupérer les 3 lignes, on doit faire une jointure `LEFT`. On peut utiliser un `CASE WHEN` pour tester si la commune est trouvée sous chaque point 75 | 76 | ```sql 77 | SELECT 78 | o.*, c.nom, ST_area(c.geom)::integer/10000 AS surface_commune, 79 | CASE 80 | WHEN c.code_insee IS NULL THEN 'pas de commune' 81 | ELSE 'ok' 82 | END AS test_commune 83 | FROM z_formation.observation AS o 84 | LEFT JOIN z_formation.commune AS c ON o.code_insee = c.code_insee 85 | WHERE True 86 | ``` 87 | 88 | Résultat 89 | 90 | | id | date | description | geom | code_insee | nom | surface_commune | test_commune | 91 | |----|------------|-------------|------|------------|----------------|-----------------|----------------| 92 | | 2 | 2020-07-08 | deux | .... | 27213 | Vexin-sur-Epte | 11434 | ok | 93 | | 1 | 2020-07-08 | un | .... | 76618 | Petit-Caux | 9243 | ok | 94 | | 3 | 2020-07-08 | trois | .... | Null | Null | Null | pas de commune | 95 | 96 | ## Les jointures spatiales 97 | 98 | Le critère de jointure peut être une **condition spatiale**. On réalise souvent une jointure par **intersection** ou par **proximité**. 99 | 100 | ### Joindre des points avec des polygones 101 | 102 | Un exemple classique de récupération des données de la table commune (nom, etc.) depuis une table de points. 103 | 104 | ```sql 105 | -- Pour chaque lieu-dit, on veut le nom de la commune 106 | SELECT 107 | l.id_lieu_dit_habite, l.nom, 108 | c.nom AS nom_commune, c.code_insee, 109 | l.geom 110 | FROM "z_formation".lieu_dit_habite AS l 111 | JOIN "z_formation".commune AS c 112 | ON st_intersects(c.geom, l.geom) 113 | ORDER BY l.nom 114 | ``` 115 | 116 | | id_lieu_dit_habite | nom | nom_commune | code_insee | geom | 117 | |--------------------|-----------------------|--------------------------|------------|------| 118 | | 58 | Abbaye du Valasse | Gruchet-le-Valasse | 76329 | .... | 119 | | 1024 | Ablemont | Bacqueville-en-Caux | 76051 | .... | 120 | | 1043 | Agranville | Douvrend | 76220 | .... | 121 | | 1377 | All des Artisans | Mesnils-sur-Iton | 27198 | .... | 122 | | 1801 | Allée des Maronniers | Heudebouville | 27332 | .... | 123 | | 1293 | Alliquerville | Trouville | 76715 | .... | 124 | | 507 | Alventot | Sainte-Hélène-Bondeville | 76587 | .... | 125 | | 555 | Alvinbuc | Veauville-lès-Baons | 76729 | .... | 126 | | 69 | Ancien hôtel de ville | Rouen | 76540 | .... | 127 | 128 | 129 | On peut facilement inverser la table principale pour afficher les lignes ordonnées par commune. 130 | 131 | ```sql 132 | SELECT 133 | c.nom, c.code_insee, 134 | l.id_lieu_dit_habite, l.nom 135 | FROM "z_formation".commune AS c 136 | JOIN "z_formation".lieu_dit_habite AS l 137 | ON st_intersects(c.geom, l.geom) 138 | ORDER BY c.nom 139 | ``` 140 | 141 | | nom | code_insee | id_lieu_dit_habite | nom | 142 | |----------|------------|--------------------|--------------------| 143 | | Aclou | 27001 | 107 | Manoir de la Haule | 144 | | Acquigny | 27003 | 106 | Manoir de Becdal | 145 | | Ailly | 27005 | 596 | Quaizes | 146 | | Ailly | 27005 | 595 | Ingremare | 147 | | Ailly | 27005 | 594 | Gruchet | 148 | | Alizay | 27008 | 667 | Le Solitaire | 149 | | Ambenay | 27009 | 204 | Les Siaules | 150 | | Ambenay | 27009 | 201 | Les Renardieres | 151 | | Ambenay | 27009 | 202 | Le Culoron | 152 | 153 | 154 | On a plusieurs lignes par commune, autant que de lieux-dits pour cette commune. Par contre, comme ce n'est pas une jointure `LEFT`, on ne trouve que des résultats pour les communes qui ont des lieux-dits. 155 | 156 | On pourrait aussi faire des statistiques, en regroupant par les champs de la table principale, ici les communes. 157 | 158 | ```sql 159 | SELECT 160 | c.nom, c.code_insee, 161 | count(l.id_lieu_dit_habite) AS nb_lieu_dit, 162 | c.geom 163 | FROM "z_formation".commune AS c 164 | JOIN "z_formation".lieu_dit_habite AS l 165 | ON st_intersects(c.geom, l.geom) 166 | GROUP BY c.nom, c.code_insee, c.geom 167 | ORDER BY nb_lieu_dit DESC 168 | LIMIT 10 169 | ``` 170 | 171 | | nom | code_insee | nb_lieu_dit | geom | 172 | |--------------------|------------|-------------|------| 173 | | Heudebouville | 27332 | 61 | .... | 174 | | Mesnils-sur-Iton | 27198 | 52 | .... | 175 | | Rouen | 76540 | 20 | .... | 176 | | Saint-Saëns | 76648 | 19 | .... | 177 | | Les Grandes-Ventes | 76321 | 19 | .... | 178 | | Mesnil-en-Ouche | 27049 | 18 | .... | 179 | | Quincampoix | 76517 | 18 | .... | 180 | 181 | 182 | 183 | ### Joindre des lignes avec des polygones 184 | 185 | Récupérer le code commune de chaque chemin, par **intersection entre le chemin et la commune**. 186 | 187 | #### Jointure spatiale simple entre les géométries brutes 188 | 189 | ```sql 190 | -- Ici, on peut récupérer plusieurs fois le même chemin 191 | -- s'il passe par plusieurs communes 192 | SELECT 193 | v.*, 194 | c.nom, c.code_insee 195 | FROM "z_formation".chemin AS v 196 | JOIN "z_formation".commune AS c 197 | ON ST_Intersects(v.geom, c.geom) 198 | ORDER BY id_chemin, nom 199 | ``` 200 | 201 | Cela peut renvoyer plusieurs lignes par chemin, car chaque chemin peut passer par plusieurs communes. 202 | 203 | #### Jointure spatiale entre le centroïde des chemins et la géométrie des communes 204 | 205 | On peut utiliser le **centroïde de chaque chemin** pour avoir un seul objet par chemin comme résultat. 206 | 207 | ```sql 208 | -- création de l'index 209 | CREATE INDEX ON z_formation.chemin USING gist (ST_Centroid(geom)); 210 | -- Jointure spatiale 211 | -- On ne veut qu'une seule ligne par chemin 212 | -- Donc on fait l'intersection entre le centroïde des chemins (pour avoir un point) et les communes 213 | SELECT 214 | v.*, 215 | c.nom, c.code_insee 216 | FROM "z_formation".chemin AS v 217 | JOIN "z_formation".commune AS c 218 | ON ST_Intersects(ST_Centroid(v.geom), c.geom) 219 | ``` 220 | 221 | **NB:** Attention, dans ce cas, l'index spatial sur la géométrie des chemins n'est pas utilisé. C'est pour cela que nous avons créé un index spatial sur `ST_Centroid(geom)` pour la table des chemins. 222 | 223 | 224 | A l'inverse, on peut vouloir faire des **statistiques pour chaque commune** via jointure spatiale. Par exemple le nombre de chemins et le total des longueurs par commune. 225 | 226 | ```sql 227 | -- A l'inverse, on veut récupérer des statistiques par commune 228 | -- On veut une ligne par commune, avec des données sur les voies 229 | SELECT 230 | c.id_commune, c.nom, c.code_insee, 231 | count(v.id_chemin) AS nb_chemin, 232 | sum(st_length(v.geom)) AS somme_longueur_chemins_entiers 233 | FROM z_formation.commune AS c 234 | JOIN z_formation.chemin AS v 235 | ON st_intersects(c.geom, st_centroid(v.geom)) 236 | GROUP BY c.id_commune, c.nom, c.code_insee 237 | ; 238 | ``` 239 | 240 | #### Utilisation d'une jointure LEFT pour garder les communes sans chemins 241 | 242 | La requête précédente ne renvoie pas de lignes pour les communes qui n'ont pas de chemin dont le centroïde est dans une commune. C'est une jointure de type `INNER JOIN` 243 | 244 | Si on veut quand même récupérer ces communes, on fait une jointure `LEFT JOIN`: pour les lignes sans chemins, les champs liés à la table des chemins seront mis à `NULL`. 245 | 246 | 247 | ```sql 248 | SELECT 249 | c.id_commune, c.nom, c.code_insee, 250 | count(v.id_chemin) AS nb_chemin, 251 | sum(st_length(v.geom)) AS somme_longueur_chemins_entiers 252 | FROM z_formation.commune AS c 253 | LEFT JOIN z_formation.chemin AS v 254 | ON st_intersects(c.geom, st_centroid(v.geom)) 255 | GROUP BY c.id_commune, c.nom, c.code_insee 256 | ; 257 | ``` 258 | 259 | C'est **beaucoup plus long**, car la requête n'utilise pas d'abord l'intersection, donc l'index spatial des communes, mais fait un parcours de toutes les lignes des communes, puis un calcul d'intersection. Pour accélérer la requête, on doit créer l'index sur les centroïdes des chemins 260 | 261 | ```sql 262 | CREATE INDEX ON z_formation.chemin USING GIST(ST_Centroid(geom)) 263 | ``` 264 | 265 | puis la relancer. Dans cet exemple, on passe de 100 secondes à 1 seconde, grâce à ce nouvel index spatial. 266 | 267 | #### Affiner le résultat en découpant les chemins 268 | 269 | Dans la requête précédente, on calculait la longueur totale de chaque chemin, pas le **morceau exacte qui est sur chaque commune**. Pour cela, on va utiliser la fonction `ST_Intersection`. La requête va être plus coûteuse, car il faut réaliser le découpage des lignes des chemins par les polygones des communes. 270 | 271 | On va découper exactement les chemins par commune et récupérer les informations 272 | 273 | ```sql 274 | CREATE TABLE z_formation.decoupe_chemin_par_commune AS 275 | -- Découper les chemins par commune 276 | SELECT 277 | -- id unique 278 | -- infos du chemin 279 | l.id AS id_chemin, 280 | -- infos de la commune 281 | c.nom, c.code_insee, 282 | ST_Multi(st_collectionextract(ST_Intersection(c.geom, l.geom), 2))::geometry(multilinestring, 2154) AS geom 283 | FROM "z_formation".commune AS c 284 | JOIN "z_formation".chemin AS l 285 | ON st_intersects(c.geom, l.geom) 286 | ; 287 | CREATE INDEX ON z_formation.decoupe_chemin_par_commune USING GIST (geom); 288 | ``` 289 | 290 | 291 | **NB**: Attention à ne pas confondre `ST_Intersects` qui renvoie vrai ou faux, et `ST_Intersection` qui renvoie la géométrie issue du découpage d'une géométrie par une autre. 292 | 293 | ### Joindre des polygones avec des polygones 294 | 295 | On peut bien sûr réaliser des **jointures spatiales** entre 2 couches de **polygones**, et découper les polygones par intersection. Attention, les performances sont forcément moins bonnes qu'avec des points. 296 | 297 | Trouver l'ensemble des zonages PLU pour les parcelles du Havre. 298 | 299 | On va récupérer **plusieurs résultats pour chaque parcelle** si plusieurs zonages chevauchent une parcelle. 300 | 301 | ```sql 302 | -- Jointure spatiale 303 | SELECT 304 | p.id_parcelle, 305 | z.libelle, z.libelong, z.typezone 306 | FROM z_formation.parcelle_havre AS p 307 | JOIN z_formation.zone_urba AS z 308 | ON st_intersects(z.geom, p.geom) 309 | WHERE True 310 | ``` 311 | 312 | 313 | 314 | Compter pour chaque parcelle le nombre de zonages en intersection: on veut **une seule ligne par parcelle**. 315 | 316 | ```sql 317 | SELECT 318 | p.id_parcelle, 319 | count(z.libelle) AS nombre_zonage 320 | FROM z_formation.parcelle_havre AS p 321 | JOIN z_formation.zone_urba AS z 322 | ON st_intersects(z.geom, p.geom) 323 | WHERE True 324 | GROUP BY p.id_parcelle 325 | ORDER BY nombre_zonage DESC 326 | ``` 327 | 328 | Découper les parcelles par les zonages, et pouvoir calculer les surfaces des zonages, et le pourcentage par rapport à la surface de chaque parcelle. On essaye le SQL suivant: 329 | 330 | ```sql 331 | SELECT 332 | p.id_parcelle, 333 | z.libelle, z.libelong, z.typezone, 334 | -- découper les géométries 335 | st_intersection(z.geom, p.geom) AS geom 336 | FROM z_formation.parcelle_havre AS p 337 | JOIN z_formation.zone_urba AS z 338 | ON st_intersects(z.geom, p.geom) 339 | WHERE True 340 | ORDER BY p.id_parcelle 341 | ``` 342 | 343 | Il renvoie l'erreur 344 | 345 | ``` 346 | ERREUR: Error performing intersection: TopologyException: Input geom 1 is invalid: Self-intersection at or near point 492016.26000489673 6938870.663846286 at 492016.26000489673 6938870.663846286 347 | ``` 348 | 349 | On a ici des soucis de **validité de géométrie**. Il nous faut donc corriger les géométries avant de poursuivre. Voir chapitre sur la validation des géométries. 350 | 351 | Une fois les géométries validées, la requête fonctionne. On l'utilise dans une sous-requête pour créer une table et calculer les surfaces 352 | 353 | ```sql 354 | -- suppression de la table 355 | DROP TABLE IF EXISTS z_formation.decoupe_zonage_parcelle; 356 | -- création de la table avec calcul de pourcentage de surface 357 | CREATE TABLE z_formation.decoupe_zonage_parcelle AS 358 | SELECT row_number() OVER() AS id, 359 | source.*, 360 | ST_Area(geom) AS aire, 361 | 100 * ST_Area(geom) / aire_parcelle AS pourcentage 362 | FROM ( 363 | SELECT 364 | p.id_parcelle, p.id AS idpar, ST_Area(p.geom) AS aire_parcelle, 365 | z.id_zone_urba, z.libelle, z.libelong, z.typezone, 366 | -- découper les géométries 367 | (ST_Multi(st_intersection(z.geom, p.geom)))::geometry(MultiPolygon,2154) AS geom 368 | FROM z_formation.parcelle_havre AS p 369 | JOIN z_formation.zone_urba AS z ON st_intersects(z.geom, p.geom) 370 | WHERE True 371 | ) AS source; 372 | 373 | -- Ajout de la clé primaire 374 | ALTER TABLE z_formation.decoupe_zonage_parcelle ADD PRIMARY KEY (id); 375 | 376 | -- Ajout de l'index spatial 377 | CREATE INDEX ON z_formation.decoupe_zonage_parcelle USING GIST (geom); 378 | 379 | ``` 380 | 381 | ### Faire un rapport des surfaces intersectées de zonages sur une table principale 382 | 383 | Par exemple, pour chacune des communes, on souhaite calculer la somme des surfaces intersectée par chaque type de zone (parcs, znieff, etc.). 384 | 385 | Afin d'avoir à disposition des données de test pour cet exemple de rapport, nous allons créer 2 tables `z_formation.parc_national` et `z_formation.znieff`, et y insérer des fausses données: 386 | 387 | 388 | ```sql 389 | -- Table des parcs nationaux 390 | CREATE TABLE IF NOT EXISTS z_formation.parc_national ( 391 | id serial primary key, 392 | nom text, 393 | geom geometry(multipolygon, 2154) 394 | ); 395 | CREATE INDEX ON z_formation.parc_national USING GIST (geom); 396 | 397 | -- Table des znieff 398 | CREATE TABLE IF NOT EXISTS z_formation.znieff( 399 | id serial primary key, 400 | nom_znieff text, 401 | geom geometry(multipolygon, 2154) 402 | ); 403 | CREATE INDEX ON z_formation.znieff USING GIST (geom); 404 | ``` 405 | 406 | On insère des polygones dans ces deux tables: 407 | 408 | ```sql 409 | -- données de test 410 | -- parcs 411 | INSERT INTO z_formation.parc_national VALUES (1, 'un', '01060000206A0800000100000001030000000100000008000000C3F7DE73553D20411B3DC1FB0C625A410531F757E93D2041BAECB21FA85E5A41F35B09978081204195F05B9787595A41D61E4865A1A7204147BC8A3AC0605A41ED76A806317F2041A79F7E4876605A41B80752433C832041037846623A655A41E10ED595BA6120413CC1D1C18C685A41C3F7DE73553D20411B3DC1FB0C625A41'); 412 | INSERT INTO z_formation.parc_national VALUES (2, 'deux', '01060000206A080000010000000103000000010000000900000024D68B4AE0412141AAAAAA3C685B5A4130642ACBD01421413A85AE4B72585A41CA08F0240E382141746C4BD107535A41FA30F7A78A4A2141524A29E544555A414796BF5CE63621414DD2E222A4565A416B92160F9B5D2141302807F981575A4130DC700B2E782141DC0ED50B6B5C5A4106FBB8C8294F214150AC17BF015E5A4124D68B4AE0412141AAAAAA3C685B5A41'); 413 | INSERT INTO z_formation.parc_national VALUES (3, 'trois', '01060000206A0800000100000001030000000100000006000000918DCFE7E0861F4137AB79AF14515A411AE56040588A1F41642A43EEC74F5A41DF2EBB3CEBA41F418C31C66ADA4F5A4168864C9562A81F416E87EA40B8505A415CBC8A74C3A31F410FA4F63202515A41918DCFE7E0861F4137AB79AF14515A41'); 414 | INSERT INTO z_formation.parc_national VALUES (4, 'quatre', '01060000206A080000010000000103000000010000000500000004474FE81DBA2041269A684EFD625A41AB17C51223C9204120B507BEAD605A4116329539BBF22041A3273886D5615A416F611F0FB6E32041FA1A9F0F4A645A4104474FE81DBA2041269A684EFD625A41'); 415 | INSERT INTO z_formation.parc_national VALUES (5, 'cinq', '01060000206A0800000100000001030000000100000005000000F2E3C256231E2041E0ACE631AE535A41F7C823E772202041E89C73B6EF505A41B048BCC266362041DAC785A15E515A419E999911782F204180C9F223F8535A41F2E3C256231E2041E0ACE631AE535A41'); 416 | SELECT pg_catalog.setval('z_formation.parc_national_id_seq', 5, true); 417 | 418 | -- znieff 419 | INSERT INTO z_formation.znieff VALUES (1, 'uno', '01060000206A08000001000000010300000001000000050000004039188C39D12041770A5DF74A4A5A413A54B7FBE9CE20410C5DA7C8F5455A41811042C0A4EA204130ECE38267475A416F611F0FB6E320417125FC66FB475A414039188C39D12041770A5DF74A4A5A41'); 420 | INSERT INTO z_formation.znieff VALUES (2, 'dos', '01060000206A080000010000000103000000010000000500000076BEC6DF62492141513FFDF0525A5A417CA32770B24B21411EDBD22150595A419437ABB1F05421410F06E50CBF595A419437ABB1F0542141B022F1FE085A5A4176BEC6DF62492141513FFDF0525A5A41'); 421 | INSERT INTO z_formation.znieff VALUES (3, 'tres', '01060000206A0800000100000001030000000100000005000000A6E6CD62DF5B2141B607528F585C5A41ACCB2EF32E5E2141C5DC3FA4E95B5A414CB7438DE46A2141C5DC3FA4E95B5A41B895F013CE62214189888850A55D5A41A6E6CD62DF5B2141B607528F585C5A41'); 422 | INSERT INTO z_formation.znieff VALUES (4, 'quatro', '01060000206A0800000100000001030000000100000005000000CE857DF445102041985D7665365D5A41DA4F3F15E5142041339521C7305B5A41C2F7DE73553D2041927815D5E65A5A410393E50712252041B607528F585C5A41CE857DF445102041985D7665365D5A41'); 423 | INSERT INTO z_formation.znieff VALUES (5, 'cinco', '01060000206A080000010000000103000000010000000500000045A632DC2B702041FD25CB033C5F5A41CEFDC334A373204115EB459D0E5C5A41F25B099780812041397A8257805D5A415755558D1A7720419E42D7F5855F5A4145A632DC2B702041FD25CB033C5F5A41'); 424 | SELECT pg_catalog.setval('z_formation.znieff_id_seq', 5, true); 425 | ``` 426 | 427 | Pour chaque commune, on souhaite calculer la somme des surfaces intersectées par chaque type de zone. On doit donc utiliser toutes les tables de zonage (ici seulement 2 tables, mais c'est possible d'en ajouter) 428 | 429 | Résultat attendu: 430 | 431 | | id_commune | code_insee | nom | surface_commune_ha | somme_surface_parcs | somme_surface_znieff | 432 | |------------|------------|-------------------|--------------------|---------------------|----------------------| 433 | | 1139 | 27042 | Barville | 275.138028733401 | 87.2237204013011 | None | 434 | | 410 | 27057 | Bernienville | 779.74546553394 | None | 5.26504189468878 | 435 | | 1193 | 27061 | Berthouville | 757.19696570046 | 19.9975421896336 | None | 436 | | 495 | 27074 | Boisney | 576.995877227961 | 0.107059260396721 | None | 437 | | 432 | 27077 | Boissey-le-Châtel | 438.373848703835 | 434.510197417769 | 83.9289621127432 | 438 | 439 | 440 | * Méthode avec des sous-requêtes 441 | 442 | ```sql 443 | SELECT 444 | c.id_commune, c.code_insee, c.nom, 445 | ST_Area(c.geom) / 10000 AS surface_commune_ha, 446 | (SELECT sum(ST_Area(ST_Intersection(c.geom, p.geom)) / 10000 ) FROM z_formation.parc_national AS p WHERE ST_Intersects(p.geom, c.geom) ) AS surface_parc_national, 447 | (SELECT sum(ST_Area(ST_Intersection(c.geom, p.geom)) / 10000 ) FROM z_formation.znieff AS p WHERE ST_Intersects(p.geom, c.geom) ) AS surface_znieff 448 | FROM z_formation.commune AS c 449 | ORDER BY c.nom 450 | ``` 451 | 452 | * Méthode avec des **jointures** `LEFT` 453 | 454 | ```sql 455 | SELECT 456 | -- champs choisis dans la table commune 457 | c.id_commune, c.code_insee, c.nom, 458 | -- surface en ha 459 | ST_Area(c.geom) / 10000 AS surface_commune_ha, 460 | -- somme des découpages des parcs par commune 461 | sum(ST_Area(ST_Intersection(c.geom, p.geom)) / 10000 ) AS somme_surface_parcs, 462 | -- somme des découpages des znieff par commune 463 | sum(ST_Area(ST_Intersection(c.geom, z.geom)) / 10000 ) AS somme_surface_znieff 464 | 465 | FROM z_formation.commune AS c 466 | -- jointure spatiale sur les parcs 467 | LEFT JOIN z_formation.parc_national AS p 468 | ON ST_Intersects(c.geom, p.geom) 469 | -- jointure spatiale sur les znieff 470 | LEFT JOIN z_formation.znieff AS z 471 | ON ST_Intersects(c.geom, z.geom) 472 | 473 | -- clause WHERE optionelle 474 | -- WHERE p.id IS NOT NULL OR z.id IS NOT NULL 475 | 476 | -- on regroupe sur les champs des communes 477 | GROUP BY c.id_commune, c.code_insee, c.nom, c.geom 478 | 479 | -- on ordonne par nom 480 | ORDER BY c.nom 481 | ``` 482 | 483 | **Avantages**: 484 | 485 | * on peut intégrer facilement dans la clause `WHERE` des conditions sur les champs des tables jointes. Par exemple ne récupérer que les lignes qui sont concernées par un parc ou une znieff, via `WHERE p.id IS NOT NULL OR z.id IS NOT NULL` (commenté ci-dessus pour le désactiver) 486 | * On peut sortir plusieurs agrégats pour les tables jointes. Par exemple un décompte des parcs, un décompte des znieff 487 | 488 | ATTENTION: 489 | 490 | * on peut avoir des doublons qui vont créer des erreurs. Voir cet exemple: http://sqlfiddle.com/#!17/73485c/2/0 491 | * cette méthode peut poser des soucis de performance 492 | 493 | 494 | 495 | 496 | **ATTENTION**: 497 | 498 | * il faut absolument avoir un index spatial sur le champ `geom` de toutes les tables 499 | * le calcul de découpage des polygones des communes par ceux des zonages peut être très long (et l'index spatial ne sert à rien ici) 500 | 501 | 502 | ### Distances et tampons entre couches 503 | 504 | Pour chaque objets d'une table, on souhaite récupérer des informations sur les** objets proches d'une autre table**. Au lieu d'utiliser un tampon puis une intersection, on utilise la fonction `ST_DWithin` 505 | 506 | On prend comme exemple la table des bornes à incendie créée précédemment (remplie avec quelques données de test). 507 | 508 | Trouver toutes les parcelles **à moins de 200m** d'une borne à incendie 509 | 510 | ```sql 511 | SELECT 512 | p.id_parcelle, p.geom, 513 | b.id_borne, b.code, 514 | ST_Distance(b.geom, p.geom) AS distance 515 | FROM z_formation.parcelle_havre AS p 516 | JOIN z_formation.borne_incendie AS b 517 | ON ST_DWithin(p.geom, b.geom, 200) 518 | ORDER BY id_parcelle, id_borne 519 | ``` 520 | 521 | Attention, elle peut renvoyer **plusieurs fois la même parcelle** si 2 bornes sont assez proches. Pour ne récupérer que la borne la plus proche, on peut faire la requête suivante. La clause `DISTINCT ON` permet de dire quel champ doit être **unique** (ici id_parcelle). 522 | 523 | On **ordonne** ensuite **par ce champ et par la distance** pour prendre seulement la ligne correspondant à la parcelle **la plus proche** 524 | 525 | ```sql 526 | SELECT DISTINCT ON (p.id_parcelle) 527 | p.id_parcelle, p.geom, 528 | b.id_borne, b.code, 529 | ST_Distance(b.geom, p.geom) AS distance 530 | FROM z_formation.parcelle_havre AS p 531 | JOIN z_formation.borne_incendie AS b 532 | ON ST_DWithin(p.geom, b.geom, 200) 533 | ORDER BY id_parcelle, distance 534 | ``` 535 | 536 | Pour information, on peut vérifier en créant les tampons 537 | 538 | ```sql 539 | -- Tampons non dissous 540 | SELECT id_borne, ST_Buffer(geom, 200) AS geom 541 | FROM z_formation.borne_incendie 542 | 543 | -- Tampons dissous 544 | SELECT ST_Union(ST_Buffer(geom, 200)) AS geom 545 | FROM z_formation.borne_incendie 546 | ``` 547 | 548 | Un [article intéressant de Paul Ramsey](http://blog.cleverelephant.ca/2021/12/knn-syntax.html) sur le calcul de distance via l'opérateur `<->` pour trouver le plus proche voisin d'un objet. 549 | 550 | 551 | 552 | Continuer vers [Fusionner des géométries](./merge_geometries.md) 553 | -------------------------------------------------------------------------------- /docs/links_and_data.md: -------------------------------------------------------------------------------- 1 | # Liens utiles 2 | 3 | ## Documentation 4 | 5 | Documentation de PostgreSQL : https://docs.postgresql.fr/current/ 6 | 7 | Documentation des fonctions PostGIS: 8 | 9 | * en anglais : https://postgis.net/docs/reference.html 10 | * en français https://postgis.net/docs/postgis-fr.html notamment la référence des fonctions spatiales : https://postgis.net/docs/postgis-fr.html#reference 11 | 12 | ## Base de données 13 | 14 | Nous présupposons qu'une **base de données** est accessible pour la formation, via un **rôle PostgreSQL** avec des droits élevés (notamment pour créer des schémas et des tables). L'extension **PostGIS** doit aussi être activée sur cette base de données. 15 | 16 | ## Jeux de données 17 | 18 | Pour cette formation, nous utilisons des données libres de droit : 19 | 20 | * Un dump est téléchargeable en cliquant sur ce [lien](https://github.com/3liz/formation-postgis/releases/download/1.0/data_formation.dump). 21 | 22 | Il peut est chargé en base avec cette commande : 23 | ```bash 24 | pg_restore -h URL_SERVEUR -p 5432 -U NOM_UTILISATEUR -d NOM_BASE --no-owner --no-acl data_formation.dump 25 | ``` 26 | 27 | Ce jeu de données a pour sources : 28 | 29 | * Extraction de données d'**OpenStreetMap** dans un format SIG, sous licence "ODBL" (site https://github.com/igeofr/osm2igeo ). On utilisera par exemple les données de l'ancienne région Haute-Normandie. 30 | 31 | * Données cadastrales (site https://cadastre.data.gouv.fr ), sous licence "Licence Ouverte 2.0" Par exemple pour la Seine-Maritime : 32 | https://cadastre.data.gouv.fr/data/etalab-cadastre/2024-10-01/shp/departements/76/ 33 | 34 | * PLU (site https://www.geoportail-urbanisme.gouv.fr/map/ ). Par exemple les données de la ville du Havre. Cliquer sur la commune, et utiliser le lien de téléchargement. 35 | 36 | Ces données peuvent aussi être importées dans la base de formation via les outils de QGIS. 37 | 38 | ## Concepts de base de données 39 | 40 | Un rappel sur les concepts de table, champs, relations. 41 | 42 | * Documentation de QGIS : https://docs.qgis.org/latest/fr/docs/training_manual/database_concepts/index.html 43 | 44 | 45 | ## Quelques extensions QGIS 46 | 47 | [Lire la formation QGIS également](https://3liz.github.io/formation-qgis/extensions.html) 48 | 49 | * **Autosaver** : sauvegarde automatique du projet QGIS toutes les N minutes 50 | * **Layer Board** : liste l'ensemble des couches du projet et permet de modifier des caractéristiques pour plusieurs couches à la fois 51 | * **Cadastre** : import et exploitation des données EDIGEO ET MAJIC dans PostgreSQL 52 | 53 | Continuer vers [Gestion des données PostgreSQL dans QGIS](./postgresql_in_qgis.md) 54 | -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/media/qgis_connexion_PostgreSQL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3liz/formation-postgis/585f720bed11820fc22cc555fe96614bc7bd149c/docs/media/qgis_connexion_PostgreSQL.png -------------------------------------------------------------------------------- /docs/media/qgis_creer_schema_explorateur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3liz/formation-postgis/585f720bed11820fc22cc555fe96614bc7bd149c/docs/media/qgis_creer_schema_explorateur.png -------------------------------------------------------------------------------- /docs/media/qgis_creer_table_explorateur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3liz/formation-postgis/585f720bed11820fc22cc555fe96614bc7bd149c/docs/media/qgis_creer_table_explorateur.png -------------------------------------------------------------------------------- /docs/media/qgis_rendu_simplification_fournisseur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3liz/formation-postgis/585f720bed11820fc22cc555fe96614bc7bd149c/docs/media/qgis_rendu_simplification_fournisseur.png -------------------------------------------------------------------------------- /docs/media/qgis_traitement_exporter_dialogue_algorithme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3liz/formation-postgis/585f720bed11820fc22cc555fe96614bc7bd149c/docs/media/qgis_traitement_exporter_dialogue_algorithme.png -------------------------------------------------------------------------------- /docs/media/qgis_traitement_exporter_postgresql_ogr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3liz/formation-postgis/585f720bed11820fc22cc555fe96614bc7bd149c/docs/media/qgis_traitement_exporter_postgresql_ogr.png -------------------------------------------------------------------------------- /docs/merge_geometries.md: -------------------------------------------------------------------------------- 1 | # Fusionner des géométries 2 | 3 | On souhaite créer une seule géométrie qui est issue de la **fusion de toutes les géométries** regroupées par un critère (nature, code, etc.) 4 | 5 | Par exemple un polygone fusionnant les zonages qui partagent le même type 6 | 7 | ```sql 8 | SELECT count(id_zone_urba) AS nb_objets, typezone, 9 | ST_Union(geom) AS geom 10 | FROM z_formation.zone_urba 11 | GROUP BY typezone 12 | ``` 13 | 14 | On souhaite parfois **fusionner toutes les géométries qui sont jointives**. 15 | Par exemple, on veut fusionner **toutes les parcelles jointives** pour créer des blocs. 16 | 17 | ```sql 18 | DROP TABLE IF EXISTS z_formation.bloc_parcelle_havre; 19 | CREATE TABLE z_formation.bloc_parcelle_havre AS 20 | SELECT 21 | row_number() OVER() AS id, 22 | string_agg(id::text, ', ') AS ids, t.geom::geometry(polygon, 2154) AS geom 23 | FROM ( 24 | SELECT 25 | (St_Dump(ST_Union(a.geom))).geom AS geom 26 | FROM z_formation.parcelle_havre AS a 27 | WHERE ST_IsValid(a.geom) 28 | ) t 29 | JOIN z_formation.parcelle_havre AS p 30 | ON ST_Intersects(p.geom, t.geom) 31 | GROUP BY t.geom 32 | ; 33 | ALTER TABLE z_formation.bloc_parcelle_havre ADD PRIMARY KEY (id); 34 | CREATE INDEX ON z_formation.bloc_parcelle_havre USING GIST (geom); 35 | ``` 36 | 37 | Continuer vers [Les triggers](./triggers.md) 38 | -------------------------------------------------------------------------------- /docs/perform_calculation.md: -------------------------------------------------------------------------------- 1 | # Faire des calculs 2 | 3 | ## Calcul sur des attributs 4 | 5 | Le SQL permet de réaliser des calculs ou des modifications à partir de champs. On peut donc faire des calculs sur des nombres, ou des modifications (remplacement de texte, mise en majuscule, etc.) 6 | 7 | Faire un calcul très simple, avec des opérateurs `+ - /` et `*`, ainsi que des parenthèses 8 | 9 | ```sql 10 | -- On multiplie 10 par 2 11 | SELECT 12 | 10 * 2 AS vingt, 13 | (2.5 -1) * 10 AS quinze 14 | ``` 15 | 16 | Il est aussi possible de faire des calculs à partir d'un ou plusieurs champs. 17 | 18 | Nous souhaitons par exemple créer un champ qui contiendra la **population** des communes. Dans la donnée source, le champ `popul` est de type chaîne de caractère, car il contient parfois la valeur `'NC'` lorsque la population n'est pas connue. 19 | 20 | Nous ne pouvons pas faire de calculs à partir d'un champ texte. On souhaite donc **créer un nouveau champ** population pour y stocker les valeurs entières. 21 | 22 | ```sql 23 | -- Ajout d'un champ de type entier dans la table 24 | ALTER TABLE z_formation.commune ADD COLUMN population integer; 25 | ``` 26 | 27 | **Modifier** le nouveau champ population pour y mettre la valeur entière lorsqu'elle est connue. La modification d'une table se fait avec la requête `UPDATE`, en passant les champs à modifier et leur nouvelle valeur via `SET` 28 | 29 | ```sql 30 | -- Mise à jour d'un champ à partir d'un calcul 31 | UPDATE z_formation.commune SET population = 32 | CASE 33 | WHEN popul != 'NC' THEN popul::integer 34 | ELSE NULL 35 | END 36 | ; 37 | ``` 38 | 39 | Dans cette requête, le `CASE WHEN condition THEN valeur ELSE autre_valeur END` permet de faire un test sur la valeur d'origine, et de proposer une valeur si la condition est remplie ( https://sql.sh/cours/case ) 40 | 41 | Une fois ce champ `population` renseigné correctement, dans un type entier, on peut réaliser un calcul très simple, par exemple **doubler la population**: 42 | 43 | ```sql 44 | -- Calcul simple : on peut utiliser les opérateurs mathématiques 45 | SELECT id_commune, code_insee, nom, geom, 46 | population, 47 | population * 2 AS double_population 48 | FROM z_formation.commune 49 | LIMIT 10 50 | ``` 51 | 52 | Il est possible de **combiner plusieurs champs** pour réaliser un calcul. Nous verrons plus loin comment calculer la **densité de population** à partir de la population et de la surface des communes. 53 | 54 | ## Calculer des caractéristiques spatiales 55 | 56 | Par exemple la **longueur** ou la **surface** 57 | 58 | Calculer la longueur d'objets linéaires 59 | 60 | ```sql 61 | -- Calcul des longueurs de route 62 | SELECT id_route, id, nature, 63 | ST_Length(geom) AS longueur_m 64 | FROM z_formation.route 65 | LIMIT 100 66 | ``` 67 | 68 | Calculer la **surface** de polygones, et utiliser ce résultat dans un calcul. Par exemple ici la **densité de population**: 69 | 70 | ```sql 71 | -- Calculer des données à partir de champs et de fonctions spatiales 72 | SELECT id_commune, code_insee, nom, geom, 73 | population, 74 | ST_Area(geom) AS surface, 75 | population / ( ST_Area(geom) / 1000000 ) AS densite_hab_km 76 | FROM z_formation.commune 77 | LIMIT 10 78 | ``` 79 | 80 | ## Créer des géométries à partir de géométries 81 | 82 | On peut modifier les géométries avec des fonctions spatiales, ce qui revient à effectuer un calcul sur les géométries. Deux exemples classiques : **centroides** et **tampons** 83 | 84 | Calculer le **centroïde** de polygones 85 | 86 | ```sql 87 | -- Centroides des communes 88 | SELECT id_commune, code_insee, nom, 89 | ST_Centroid(geom) AS geom 90 | FROM z_formation.commune 91 | ``` 92 | 93 | Le centroïde peut ne pas être à l'intérieur du polygone, par exemple sur la commune de **Arnières-sur-Iton**. 94 | Forcer le **centroïde à l'intérieur du polygone**. Attention, ce calcul est plus long. 95 | [Si vous souhaitez mieux comprendre l'algorithme derrière cette fonction](https://gis.stackexchange.com/questions/76498/how-is-st-pointonsurface-calculated) 96 | 97 | ```sql 98 | -- Centroïdes à l'intérieur des communes 99 | -- Attention, c'est plus long à calculer 100 | SELECT id_commune, code_insee, nom, 101 | ST_PointOnSurface(geom) AS geom 102 | FROM z_formation.commune 103 | ``` 104 | 105 | Calculer le **tampon** autour d'objets 106 | 107 | ```sql 108 | -- Tampons de 1km autour des communes 109 | SELECT id_commune, nom, population, 110 | ST_Buffer(geom, 1000) AS geom 111 | FROM z_formation.commune 112 | LIMIT 10 113 | ``` 114 | 115 | Continuer vers [Filtrer des données: WHERE](./filter_data.md) 116 | -------------------------------------------------------------------------------- /docs/postgresql_in_qgis.md: -------------------------------------------------------------------------------- 1 | # Gestion des données PostgreSQL dans QGIS 2 | 3 | ## Introduction 4 | 5 | Lorsqu'on travaille avec des données **PostgreSQL**, QGIS n'accède pas à la donnée en lisant un ou plusieurs fichiers, mais fait des **requêtes** à la base, à chaque fois qu'il en a besoin: déplacement de carte, zoom, ouverture de la table attributaire, sélection par expression, etc. 6 | 7 | * QGIS **se connecte** à la base de données, et récupère des données qui sont stockées dans des tables. Il doit donc **télécharger la donnée** à chaque action (pas de cache car la donnée peut changer entre temps). 8 | * une **table** équivaut à une **couche SIG**, définie par un nom, une **liste de champs typés**, et un ou plusieurs champs de **géométrie**. 9 | * une **géométrie** est caractérisée par un **type** (polygone, point, ligne, etc.), une **dimension** (2D ou 3D) et une **projection** (Ex: EPSG:2154) codifiée via un SRID (Ex: 2154) 10 | * certaines tables n'ont pas de géométrie: on les appelle alors **non spatiales**. QGIS sait les exploiter, ce qui permet de stocker des informations de contexte (nomenclature, événements). 11 | 12 | La base de données fournit donc un lieu de stockage des données centralisé. On peut gérer les droits d'accès ou d'écriture sur les schémas et les tables. 13 | 14 | 15 | ## Créer une connexion QGIS à la base de données 16 | 17 | Dans QGIS, il faut **créer une nouvelle connexion** à PostgreSQL, via l'outil "Éléphant" : menu **Couches / Ajouter une couche / Ajouter une couche PostgreSQL**. Configurer les options suivantes : 18 | 19 | * laisser le champ **Service** vide (sauf si vous savez utiliser les fichiers de service PostgreSQL, ce qui est recommandé) 20 | * cocher les cases **Enregistrer** à côté de l'utilisateur et du mot de passe, après avoir **Tester la connexion** (via le bouton dédié) 21 | * cocher les cases en bas **Lister les tables sans géométries** et **Utiliser la table de métadonnées estimées** 22 | * Valider 23 | 24 | ![Nouvelle connexion PostGIS dans QGIS](media/qgis_connexion_PostgreSQL.png) 25 | 26 | **Attention** Pour plus de sécurité, privilégier l'usage d'un service PostgreSQL: 27 | https://docs.qgis.org/latest/fr/docs/user_manual/managing_data_source/opening_data.html#pg-service-file (plugin QGIS intéressant : PG Service Parser) 28 | 29 | Il est aussi intéressant pour les **performances** d'accès aux données PostgreSQL de modifier une option dans les options de QGIS, onglet **Rendu** : il faut cocher la case **Réaliser la simplification par le fournisseur de données lorsque c'est possible**. Cela permet de télécharger des versions allégées des données aux petites échelles. [Documentation QGIS](https://docs.qgis.org/latest/fr/docs/user_manual/introduction/qgis_configuration.html#rendering-settings) 30 | 31 | ![Simplification rendu vecteur](media/qgis_rendu_simplification_fournisseur.png) 32 | 33 | **NB** Pour les couches PostGIS qui auraient déjà été ajoutées **avant d'avoir activé cette option**, vous pouvez manuellement changer dans vos projets via l'onglet **Rendu** de la boîte de dialogue des propriétés de chaque couche PostGIS. 34 | 35 | 36 | ## Ouvrir une couche PostgreSQL dans QGIS 37 | 38 | Trois solutions sont possibles : 39 | 40 | * **utiliser l'explorateur** : Le panneau présente un arbre qui liste les `schémas`, puis les `tables` ou `vues` exploitables. Une icône devant chaque table/vue indique si une table est géométrique ou non ainsi que le type de géométrie, point, ligne ou polygone. On peut utiliser le menu `Clic-Droit` sur les objets de l'arbre. 41 | * utiliser le menu **Couche / Ajouter une couche**. La boite de dialogue propose de se connecter, puis liste les schémas et les tables (ancienne méthode pas recommandée) 42 | * utiliser le **Gestionnaire de base de données**, qui présente une fenêtre QGIS séparée dédiée aux manipulations sur les données. 43 | 44 | ## Création de schémas et de tables 45 | 46 | On peut travailler avec le gestionnaire de bases de données de QGIS : menu **Base de données > Gestionnaire BD** (sinon via l'icône de la barre d’outil base de données) ou avec l'**explorateur** (recommandé). 47 | 48 | Dans l'arbre qui se présente, on peut **choisir sa connexion**, puis double-cliquer, ce qui montre l'ensemble des **schémas**, et l'ouverture d'un schéma montre la liste des tables et vues. Les menus permettent de créer ou d'éditer des objets (schémas, tables). 49 | 50 | Une **fenêtre SQL** permet de lancer manuellement des requêtes SQL. Nous allons principalement utiliser cet outil : menu **Base de données / Fenêtre SQL** (on peut aussi le lancer via F2). 51 | 52 | NB: C'est possible aussi d'utiliser le **fenêtre SQL de l'explorateur** via clic-droit `Exécuter le SQL ...`, mais elle ne permet pas encore de ne lancer que le **texte surligné**, ce qui est pourtant très pratique pendant une formation. 53 | 54 | ### Création du schéma 55 | 56 | Les **schémas** dans une base PostgreSQL sont utiles pour regrouper les tables. 57 | 58 | On recommande de ne pas créer de tables dans le schéma `public`, mais d'utiliser des schémas (par thématique, pour la gestion des droits, etc.). 59 | 60 | Pour la formation, nous allons créer un schéma `z_formation` : 61 | 62 | * Dans l'explorateur, faire un clic-droit sur le nom de la connexion et `Créer un schéma`. 63 | 64 | ![Créer un schéma](media/qgis_creer_schema_explorateur.png) 65 | 66 | ### Création d'une table 67 | 68 | Ensuite, on peut créer une **table** dans ce schéma : dans l'explorateur, faire un clic-droit sur le schéma `z_formation`, puis `Nouvelle table...` : 69 | 70 | * choisir le **schéma** et le **nom** de la table, en minuscule, sans accents ni caractères complexes 71 | * Via le bouton **Ajouter un champ**, on crée autant de champs que nécessaire en choisissant le nom et le type. **Choisir des noms de champ simples sans majuscule, espace ni accents !**. 72 | * Choisir dans la liste déroulante le **champ de clé primaire** (ici id) 73 | * Cocher **Créer une colonne géométrique** et choisir le type et le SRID (par exemple 2154 pour le Lambert 93) 74 | * Cocher **Créer un index spatial** 75 | 76 | 77 | ![Créer une table](media/qgis_creer_table_explorateur.png) 78 | 79 | **NB**: on a créé une table dans cet exemple `z_formation.borne_incendie` avec les champs **code** (text), **debit** (real) et **geom** (géométrie de type Point, code SRID 2154) 80 | 81 | * Un champ `id` de type **entier auto-incrémenté** a été créé automatiquement par QGIS en tant que **clé primaire** de la table. 82 | * Un **index spatial** a aussi été créé par QGIS sur le champ de géométrie. 83 | 84 | ### Utiliser du SQL au lieu des menus de QGIS 85 | 86 | On peut aussi utiliser du **SQL** pour créer des objets dans la base : 87 | 88 | ```sql 89 | -- création d'un schéma 90 | CREATE SCHEMA IF NOT EXISTS z_formation; 91 | 92 | -- création de la table 93 | CREATE TABLE IF NOT EXISTS z_formation.borne_incendie ( 94 | -- un serial est un entier auto-incrémenté 95 | id_borne serial NOT NULL PRIMARY KEY, 96 | code text NOT NULL, 97 | debit real, 98 | geom geometry(Point, 2154) 99 | ); 100 | -- Création de l'index spatial 101 | DROP INDEX IF EXISTS borne_incendie_geom_idx; 102 | CREATE INDEX ON z_formation.borne_incendie USING GIST (geom); 103 | 104 | ``` 105 | 106 | ### Ajouter des données dans une table 107 | 108 | On peut bien sûr charger la table dans QGIS, puis utiliser les **outils d'édition** classique pour créer des nouveaux objets ou les modifier. 109 | 110 | En SQL, il est aussi possible d'insérer des données ( https://sql.sh/cours/insert-into ). Par exemple pour les bornes à incendie : 111 | 112 | 113 | ```sql 114 | INSERT INTO z_formation.borne_incendie (code, debit, geom) 115 | VALUES 116 | ('ABC', 1.5, ST_SetSRID(ST_MakePoint(490846.0,6936902.7), 2154)), 117 | ('XYZ', 4.1, ST_SetSRID(ST_MakePoint(491284.9,6936551.6), 2154)), 118 | ('FGH', 2.9, ST_SetSRID(ST_MakePoint(490839.8,6937794.8), 2154)), 119 | ('IOP', 3.6, ST_SetSRID(ST_MakePoint(491203.3,6937488.1), 2154)) 120 | ; 121 | ``` 122 | 123 | **NB**: Nous verrons plus loin l'utilisation de fonctions de création de géométrie, comme **ST_MakePoint** 124 | 125 | 126 | ## Vérifier et créer les indexes spatiaux 127 | 128 | On peut vérifier si chaque table contient un **index spatial** via le gestionnaire de base de données de QGIS, en cliquant sur la table dans l'arbre, puis en regardant les informations de l'onglet **Info**. On peut alors créer l'index spatial via le lien bleu **Aucun index spatial défini (en créer un)**. 129 | 130 | Sinon, il est possible de le faire en SQL via la requête suivante : 131 | 132 | ```sql 133 | CREATE INDEX ON nom_du_schema.nom_de_la_table USING GIST (geom); 134 | ``` 135 | 136 | Si on souhaite automatiser la création des indexes pour toutes les tables qui n'en ont pas, on peut utiliser une fonction, décrite dans la partie [Fonctions utiles](./utils.md) 137 | 138 | Continuer vers l'[Import des données dans PostgreSQL](./import_data.md) 139 | -------------------------------------------------------------------------------- /docs/save_queries.md: -------------------------------------------------------------------------------- 1 | # Enregistrer une requête 2 | 3 | ## Les vues 4 | 5 | Une vue est l'enregistrement d'une requête, appelée **définition de la vue**, qui est stocké dans la base, et peut être **utilisée comme une table**. 6 | 7 | Créer une vue via `CREATE VIEW` 8 | 9 | ```sql 10 | -- On supprime d'abord la vue si elle existe 11 | DROP VIEW IF EXISTS z_formation.v_voies; 12 | -- On crée la vue en récupérant les routes de plus de 5 km 13 | CREATE VIEW z_formation.v_voies AS 14 | SELECT id_route, id AS code, ST_Length(geom) AS longueur, geom 15 | FROM z_formation.route 16 | WHERE ST_Length(geom) > 5000 17 | ``` 18 | 19 | Utiliser cette vue dans une autre requête 20 | 21 | * pour filtrer les données 22 | 23 | ```sql 24 | -- Ou filtrer les données 25 | SELECT * FROM z_formation.v_voies 26 | WHERE longueur > 10000 27 | ``` 28 | 29 | ## Enregistrer une requête comme une table 30 | 31 | C'est la même chose que pour enregistrer une vue, sauf qu'on crée une table: les données sont donc stockées en base, et n'évoluent plus en fonction des données source. Cela permet d'accéder rapidement aux données, car la requête sous-jacente n'est plus exécutée une fois la table créée. 32 | 33 | ### Exemple 1 - créer la table des voies rassemblant les routes et les chemins 34 | 35 | ```sql 36 | DROP TABLE IF EXISTS z_formation.t_voies; 37 | CREATE TABLE z_formation.t_voies AS 38 | SELECT 39 | -- on récupère tous les champs 40 | source.*, 41 | -- on calcule la longueur après rassemblement des données 42 | ST_Length(geom) AS longueur 43 | FROM ( 44 | (SELECT id, geom 45 | FROM z_formation.chemin 46 | LIMIT 100) 47 | UNION ALL 48 | (SELECT id, geom 49 | FROM z_formation.route 50 | LIMIT 100) 51 | ) AS source 52 | ORDER BY longueur 53 | ; 54 | ``` 55 | 56 | Comme c'est une table, il est intéressant d'ajouter un index spatial. 57 | 58 | ```sql 59 | CREATE INDEX ON z_formation.t_voies USING GIST (geom); 60 | ``` 61 | 62 | On peut aussi ajouter une clé primaire 63 | 64 | ```sql 65 | ALTER TABLE z_formation.t_voies ADD COLUMN gid serial; 66 | ALTER TABLE z_formation.t_voies ADD PRIMARY KEY (gid); 67 | ``` 68 | 69 | **Attention** Les données de la table n'évoluent plus en fonction des données des tables source. Il faut donc supprimer la table puis la recréer si besoin. Pour répondre à ce besoin, il existe les **vues matérialisées**. 70 | 71 | 72 | 73 | 74 | ### Exemple 2 - créer une table de nomenclature à partir des valeurs distinctes d'un champ. 75 | 76 | On crée la table si besoin. On ajoutera ensuite les données via `INSERT` 77 | 78 | ```sql 79 | -- Suppression de la table 80 | DROP TABLE IF EXISTS z_formation.nomenclature; 81 | -- Création de la table 82 | CREATE TABLE z_formation.nomenclature ( 83 | id serial primary key, 84 | code text, 85 | libelle text, 86 | ordre smallint 87 | ); 88 | 89 | ``` 90 | 91 | On ajoute ensuite les données. La clause `WITH` permet de réaliser une sous-requête, et de l'utiliser ensuite comme une table. La clause `INSERT INTO` permet d'ajouter les données. On ne lui passe pas le champ id, car c'est un **serial**, c'est-à-dire un entier **auto-incrémenté**. 92 | 93 | ```sql 94 | -- Ajout des données à partir d'une table via commande INSERT 95 | INSERT INTO z_formation.nomenclature 96 | (code, libelle, ordre) 97 | -- Clause WITH pour récupérer les valeurs distinctes comme une table virtuelle 98 | WITH source AS ( 99 | SELECT DISTINCT 100 | nature AS libelle 101 | FROM z_formation.lieu_dit_habite 102 | WHERE nature IS NOT NULL 103 | ORDER BY nature 104 | ) 105 | -- Sélection des données dans cette table virtuelle "source" 106 | SELECT 107 | -- on crée un code à partir de l'ordre d'arrivée. 108 | -- row_number() OVER() permet de récupérer l'identifiant de la ligne dans l'ordre d'arrivée 109 | -- (un_champ)::text permet de convertir un champ ou un calcul en texte 110 | -- lpad permet de compléter le chiffre avec des zéro. 1 devient 01 111 | lpad( (row_number() OVER())::text, 2, '0' ) AS code, 112 | libelle, 113 | row_number() OVER() AS ordre 114 | FROM source 115 | ; 116 | ``` 117 | 118 | Le résultat est le suivant: 119 | 120 | | code | libelle | ordre | 121 | |------|-----------------|-------| 122 | | 01 | Château | 1 | 123 | | 02 | Lieu-dit habité | 2 | 124 | | 03 | Moulin | 3 | 125 | | 04 | Quartier | 4 | 126 | | 05 | Refuge | 5 | 127 | | 06 | Ruines | 6 | 128 | 129 | 130 | ### Exemple 3 - créer une table avec l'extraction des parcelles sur une commune 131 | 132 | On utilise le champ `commune` pour filtrer. On n'oublie pas de créer l'index spatial, qui sera utilisé pour améliorer les performances lors des jointures spatiales. 133 | 134 | ```sql 135 | -- supprimer la table si elle existe déjà 136 | DROP TABLE IF EXISTS z_formation.parcelle_havre ; 137 | 138 | -- Créer la table via filtre sur le champ commune 139 | CREATE TABLE z_formation.parcelle_havre AS 140 | SELECT p.* 141 | FROM z_formation.parcelle AS p 142 | WHERE p.commune = '76351'; 143 | 144 | -- Ajouter la clé primaire 145 | ALTER TABLE z_formation.parcelle_havre ADD PRIMARY KEY (id_parcelle); 146 | 147 | -- Ajouter l'index spatial 148 | CREATE INDEX ON z_formation.parcelle_havre USING GIST (geom); 149 | ``` 150 | 151 | ## Enregistrer une requête comme une vue matérialisée 152 | 153 | 154 | ```sql 155 | -- On supprime d'abord la vue matérialisée si elle existe 156 | DROP MATERIALIZED VIEW IF EXISTS z_formation.vm_voies; 157 | -- On crée la vue en récupérant les routes de plus de 5 km 158 | CREATE MATERIALIZED VIEW z_formation.vm_voies AS 159 | SELECT id_route, id AS code, ST_Length(geom) AS longueur, geom 160 | FROM z_formation.route 161 | WHERE ST_Length(geom) > 6000 162 | 163 | -- Ajout des indexes sur le champ id_route et de géométrie 164 | CREATE INDEX ON z_formation.vm_voies (id_route); 165 | CREATE INDEX ON z_formation.vm_voies USING GIST (geom); 166 | 167 | -- On rafraîchit la vue matérialisée quand on en a besoin 168 | -- par exemple quand les données source ont été modifiées 169 | REFRESH MATERIALIZED VIEW z_formation.vm_voies; 170 | 171 | ``` 172 | 173 | Continuer vers [Réaliser des jointures attributaires et spatiales; JOIN](./join_data.md) 174 | -------------------------------------------------------------------------------- /docs/sql_select.md: -------------------------------------------------------------------------------- 1 | # Sélectionner 2 | 3 | Nous allons présenter des **requêtes SQL** de plus en plus complexes pour accéder aux données, et exploiter les capacités de PostgreSQL/PostGIS. Une requête est construite avec des instructions standardisées, appelées **clauses** 4 | 5 | ```sql 6 | -- Ordre des clauses SQL 7 | SELECT une_colonne, une_autre_colonne 8 | FROM nom_du_schema.nom_de_la_table 9 | (LEFT) JOIN autre_schema.autre_table 10 | ON critere_de_jointure 11 | WHERE condition 12 | GROUP BY champs_de_regroupement 13 | ORDER BY champs_d_ordre 14 | LIMIT 10 15 | 16 | ``` 17 | Récupérer tous les objets d'une table, et les valeurs pour toutes les colonnes 18 | 19 | ```sql 20 | -- Sélectionner l'ensemble des données d'une couche: l'étoile veut dire "tous les champs de la table" 21 | SELECT * 22 | FROM z_formation.borne_incendie 23 | ; 24 | ``` 25 | 26 | Les 10 premiers objets 27 | 28 | ```sql 29 | -- Sélectionner les 10 premières communes par ordre alphabétique 30 | SELECT * 31 | FROM z_formation.commune 32 | ORDER BY nom 33 | LIMIT 10 34 | ``` 35 | 36 | Les 10 premiers objets par ordre alphabétique 37 | 38 | ```sql 39 | -- Sélectionner les 10 premières communes par ordre alphabétique descendant 40 | SELECT * 41 | FROM z_formation.commune 42 | ORDER BY nom DESC 43 | LIMIT 10 44 | ``` 45 | 46 | Les 10 premiers objets avec un ordre sur plusieurs champs 47 | 48 | ```sql 49 | -- On peut utiliser plusieurs champs pour l'ordre 50 | SELECT * 51 | FROM z_formation.commune 52 | ORDER BY depart, nom 53 | LIMIT 10 54 | ``` 55 | 56 | Sélectionner seulement certains champs 57 | 58 | ```sql 59 | -- Sélectionner seulement certains champs, et avec un ordre 60 | SELECT id_commune, code_insee, nom 61 | FROM z_formation.commune 62 | ORDER BY nom 63 | ``` 64 | 65 | Donner un alias (un autre nom) aux champs 66 | 67 | ```sql 68 | -- Donner des alias aux noms des colonnes 69 | SELECT id_commune AS identifiant, 70 | code_insee AS "code_commune", 71 | nom 72 | FROM z_formation.commune 73 | ORDER BY nom 74 | ``` 75 | 76 | On peut donc facilement, à partir de la clause `SELECT`, choisir quels champs on souhaite récupérer, dans l'ordre voulu, et renommer le champ en sortie. 77 | 78 | 79 | ## Visualiser une requête dans QGIS 80 | 81 | Si on veut charger le résultat de la requête dans QGIS, il suffit de cocher la case **Charger en tant que nouvelle couche** puis de choisir le champ d'**identifiant unique**, et si et seulement si c'est une couche spatiale, choisir le **champ de géométrie** . 82 | 83 | Attention, si la table est non spatiale, il faut bien penser à décocher **Colonne de géométrie** ! 84 | 85 | Par exemple, pour afficher les communes avec leur information sommaire: 86 | 87 | ```sql 88 | -- Ajouter la géométrie pour visualiser les données dans QGIS 89 | SELECT id_commune AS identifiant, 90 | code_insee AS "code_commune", 91 | nom, geom 92 | FROM z_formation.commune 93 | ORDER BY nom 94 | ``` 95 | 96 | On choisira ici le champ **identifiant** comme identifiant unique, et le champ **geom** comme géométrie 97 | 98 | 99 | Continuer vers [Réaliser des calculs et créer des géométries: FONCTIONS](./perform_calculation.md) 100 | -------------------------------------------------------------------------------- /docs/triggers.md: -------------------------------------------------------------------------------- 1 | # Les triggers 2 | 3 | Les **triggers**, aussi appelés en français **déclencheurs**, permettent de lancer des actions avant ou après ajout, modification ou suppression de données sur des tables (ou des vues). 4 | 5 | Les triggers peuvent par exemple être utilisés 6 | 7 | * pour lancer le calcul de certains champs de manière automatique: date de dernière modification, utilisateur à l'origine d'un ajout 8 | * pour contrôler certaines données avant enregistrement 9 | * pour lancer des requêtes après certaines actions (historiques de modifications) 10 | 11 | Des **fonctions trigger** sont associées aux triggers. Elles peuvent être écrites en **PL/pgSQL** ou d'autres languages (p. ex. PL/Python). 12 | Une fonction trigger doit renvoyer soit NULL soit une valeur record ayant exactement la structure de la table pour laquelle le trigger a été lancé. 13 | Lire les derniers paragraphes [ici pour en savoir plus](https://docs.postgresql.fr/16/plpgsql-trigger.html#PLPGSQL-DML-TRIGGER). 14 | 15 | ## Calcul automatique de certains champs 16 | 17 | On crée une table `borne_incendie` pour pouvoir tester cette fonctionnalité: 18 | 19 | ```sql 20 | 21 | CREATE TABLE z_formation.borne_incendie ( 22 | id_borne serial primary key, 23 | code text NOT NULL, 24 | debit integer, 25 | geom geometry(point, 2154) 26 | ); 27 | CREATE INDEX ON z_formation.borne_incendie USING GIST (geom); 28 | ``` 29 | 30 | On y ajoute des champs à renseigner de manière automatique 31 | 32 | ```sql 33 | -- TRIGGERS 34 | -- Modification de certains champs après ajout ou modification 35 | -- Créer les champs dans la table 36 | ALTER TABLE z_formation.borne_incendie ADD COLUMN modif_date date; 37 | ALTER TABLE z_formation.borne_incendie ADD COLUMN modif_user text; 38 | ALTER TABLE z_formation.borne_incendie ADD COLUMN longitude real; 39 | ALTER TABLE z_formation.borne_incendie ADD COLUMN latitude real; 40 | ALTER TABLE z_formation.borne_incendie ADD COLUMN donnee_validee boolean; 41 | ALTER TABLE z_formation.borne_incendie ADD COLUMN last_action text; 42 | 43 | ``` 44 | 45 | On crée la fonction trigger qui ajoutera les métadonnées dans la table 46 | 47 | ```sql 48 | -- Créer la fonction qui sera lancée sur modif ou ajout de données 49 | CREATE OR REPLACE FUNCTION z_formation.ajout_metadonnees_modification() 50 | RETURNS TRIGGER 51 | AS $limite$ 52 | DECLARE newjsonb jsonb; 53 | BEGIN 54 | 55 | -- on transforme l'enregistrement NEW (la ligne modifiée ou ajoutée) en JSON 56 | -- pour connaître la liste des champs 57 | newjsonb = to_jsonb(NEW); 58 | 59 | -- on peut ainsi tester si chaque champ existe dans la table 60 | -- avant de modifier sa valeur 61 | -- Par exemple, on teste si le champ modif_date est bien dans l'enregistrement courant 62 | IF newjsonb ? 'modif_date' THEN 63 | NEW.modif_date = now(); 64 | RAISE NOTICE 'Date modifiée %', NEW.modif_date; 65 | END IF; 66 | 67 | IF newjsonb ? 'modif_user' THEN 68 | NEW.modif_user = CURRENT_USER; 69 | END IF; 70 | 71 | -- longitude et latitude 72 | IF newjsonb ? 'longitude' AND newjsonb ? 'latitude' 73 | THEN 74 | -- Soit on fait un UPDATE et les géométries sont différentes 75 | -- Soit on fait un INSERT 76 | -- Sinon pas besoin de calculer les coordonnées 77 | IF 78 | (TG_OP = 'UPDATE' AND NOT ST_Equals(OLD.geom, NEW.geom)) 79 | OR (TG_OP = 'INSERT') 80 | THEN 81 | NEW.longitude = ST_X(ST_Centroid(NEW.geom)); 82 | NEW.latitude = ST_Y(ST_Centroid(NEW.geom)); 83 | END IF; 84 | END IF; 85 | 86 | -- Si je trouve un champ donnee_validee, je le mets à False pour revue par l'administrateur 87 | -- Je peux faire une symbologie dans QGIS qui montre les données modifiées depuis dernière validation 88 | IF newjsonb ? 'donnee_validee' THEN 89 | NEW.donnee_validee = False; 90 | END IF; 91 | 92 | -- Si je trouve un champ last_action, je peux y mettre UPDATE ou INSERT 93 | -- Pour savoir quelle est la dernière opération utilisée 94 | IF newjsonb ? 'last_action' THEN 95 | NEW.last_action = TG_OP; 96 | END IF; 97 | 98 | RETURN NEW; 99 | END; 100 | $limite$ 101 | LANGUAGE plpgsql 102 | ; 103 | ``` 104 | 105 | On crée enfin le déclencheur pour la ou les tables souhaitées, ce qui active le lancement de la fonction trigger précédente sur certaines actions: 106 | 107 | ```sql 108 | -- Dire à PostgreSQL d'écouter les modifications et ajouts sur la table 109 | CREATE TRIGGER trg_ajout_metadonnees_modification 110 | BEFORE INSERT OR UPDATE ON z_formation.borne_incendie 111 | FOR EACH ROW EXECUTE PROCEDURE z_formation.ajout_metadonnees_modification(); 112 | ``` 113 | 114 | ## Contrôles de conformité 115 | 116 | Il est aussi possible d'utiliser les triggers pour lancer des contrôles sur les valeurs de certains champs. Par exemple, on peut ajouter un contrôle sur la géométrie lors de l'ajout ou de la modification de données: on vérifie si la géométrie est bien en intersection avec les objets de la table des communes 117 | 118 | ```sql 119 | -- Contrôle de la géométrie 120 | -- qui doit être dans la zone d'intérêt 121 | -- On crée une fonction générique qui pourra s'appliquer pour toutes les couches 122 | CREATE OR REPLACE FUNCTION z_formation.validation_geometrie_dans_zone_interet() 123 | RETURNS TRIGGER AS $limite$ 124 | BEGIN 125 | -- On vérifie l'intersection avec les communes, on renvoie une erreur si souci 126 | IF NOT ST_Intersects( 127 | NEW.geom, 128 | st_collectionextract((SELECT ST_Collect(geom) FROM z_formation.commune), 3)::geometry(multipolygon, 2154) 129 | ) THEN 130 | -- On renvoie une erreur 131 | RAISE EXCEPTION 'La géométrie doit se trouver dans les communes'; 132 | END IF; 133 | 134 | RETURN NEW; 135 | END; 136 | $limite$ 137 | LANGUAGE plpgsql; 138 | 139 | -- On l'applique sur la couches de test 140 | DROP TRIGGER IF EXISTS trg_validation_geometrie_dans_zone_interet ON z_formation.borne_incendie; 141 | CREATE TRIGGER trg_validation_geometrie_dans_zone_interet 142 | BEFORE INSERT OR UPDATE ON z_formation.borne_incendie 143 | FOR EACH ROW EXECUTE PROCEDURE z_formation.validation_geometrie_dans_zone_interet(); 144 | ``` 145 | 146 | Si on essaye de créer un point dans la table `z_formation.borne_incendie` en dehors des communes, la base renverra une erreur. 147 | 148 | 149 | ## Écrire les actions produites sur une table 150 | 151 | On crée d'abord une table qui permettra de stocker les actions 152 | 153 | ```sql 154 | 155 | CREATE TABLE IF NOT EXISTS z_formation.log ( 156 | id serial primary key, 157 | log_date timestamp, 158 | log_user text, 159 | log_action text, 160 | log_data jsonb 161 | ); 162 | ``` 163 | 164 | On peut maintenant créer un trigger qui stocke dans cette table les actions effectuées. Dans cet exemple, toutes les données sont stockées, mais on pourrait bien sûr choisir de simplifier cela. 165 | 166 | ```sql 167 | CREATE OR REPLACE FUNCTION z_formation.log_actions() 168 | RETURNS TRIGGER AS $limite$ 169 | DECLARE 170 | row_data jsonb; 171 | BEGIN 172 | -- We keep data 173 | IF TG_OP = 'INSERT' THEN 174 | -- for insert, we take the new data 175 | row_data = to_jsonb(NEW); 176 | ELSE 177 | -- for UPDATE and DELETE, we keep data before changes 178 | row_data = to_jsonb(OLD); 179 | END IF; 180 | 181 | -- We insert a new log item 182 | INSERT INTO z_formation.log ( 183 | log_date, 184 | log_user, 185 | log_action, 186 | log_data 187 | ) 188 | VALUES ( 189 | now(), 190 | CURRENT_USER, 191 | TG_OP, 192 | row_data 193 | ); 194 | IF TG_OP != 'DELETE' THEN 195 | RETURN NEW; 196 | ELSE 197 | RETURN OLD; 198 | END IF; 199 | END; 200 | $limite$ 201 | LANGUAGE plpgsql; 202 | 203 | -- On l'applique sur la couches de test 204 | -- On écoute après l'action, d'où l'utilisation de `AFTER` 205 | -- On écoute pour INSERT, UPDATE ou DELETE 206 | DROP TRIGGER IF EXISTS trg_log_actions ON z_formation.borne_incendie; 207 | CREATE TRIGGER trg_log_actions 208 | AFTER INSERT OR UPDATE OR DELETE ON z_formation.borne_incendie 209 | FOR EACH ROW EXECUTE PROCEDURE z_formation.log_actions(); 210 | 211 | ``` 212 | 213 | NB: 214 | 215 | * Attention, ce type de tables de log peut vite devenir très grosse ! 216 | * pour un log d'audit plus évolué réalisé à partir de triggers, vous pouvez consulter [le dépôt audit_trigger](https://github.com/Oslandia/audit_trigger/blob/master/audit.sql) 217 | 218 | 219 | Continuer vers [Correction des géométries invalides](./validate_geometries.md) 220 | 221 | ## Quiz 222 | 223 |
224 | Créer une table avec un champ id de type 'serial' et une géométrie de type polygone en 2154. 225 | Puis créer un trigger s'assurant que les géométries aient au minimum **4** points dessinés. 226 | 227 | 228 | ```sql 229 | -- Table: z_formation.polygone_mini_quatre_points 230 | -- DROP TABLE IF EXISTS z_formation.polygone_mini_quatre_points; 231 | CREATE TABLE IF NOT EXISTS z_formation.polygone_mini_quatre_points 232 | ( 233 | id serial NOT NULL PRIMARY KEY, 234 | geom geometry(Polygon,2154) 235 | ) 236 | 237 | -- FUNCTION: z_formation.contrainte_mini_quatre_points() 238 | -- DROP FUNCTION IF EXISTS z_formation.contrainte_mini_quatre_points(); 239 | CREATE OR REPLACE FUNCTION z_formation.contrainte_mini_quatre_points() 240 | RETURNS trigger AS $limite$ 241 | BEGIN 242 | -- On vérifie que le polygone a au moins 4 points dessinés 243 | -- => soit 5 points en comptant le dernier point qui ferme le polygone ! 244 | IF ST_NPoints(NEW.geom) < 5 245 | THEN 246 | -- On renvoie une erreur 247 | RAISE EXCEPTION 'Le polygone doit avoir au moins 4 points dessinés'; 248 | END IF; 249 | 250 | RETURN NEW; 251 | END; 252 | $limite$ 253 | LANGUAGE plpgsql; 254 | 255 | -- Trigger: trg_contrainte_mini_quatre_points 256 | -- DROP TRIGGER IF EXISTS trg_contrainte_mini_quatre_points ON z_formation.polygone_mini_quatre_points; 257 | CREATE OR REPLACE TRIGGER trg_contrainte_mini_quatre_points 258 | BEFORE INSERT OR UPDATE 259 | ON z_formation.polygone_mini_quatre_points 260 | FOR EACH ROW 261 | EXECUTE FUNCTION z_formation.contrainte_mini_quatre_points(); 262 | ``` 263 |
264 | -------------------------------------------------------------------------------- /docs/tutoriel.md: -------------------------------------------------------------------------------- 1 | # Tutoriel 2 | 3 | Afin de vous entraîner il existe différentes tutoriels en ligne vous permettant de vous exercer. 4 | 5 | - https://sql.sh/exercices-sql 6 | - https://sqlzoo.net/wiki/SQL_Tutorial 7 | - https://fxjollois.github.io/cours-sql/ 8 | - http://webtic.free.fr/sql/exint/q1.htm 9 | - https://www.hackerrank.com/domains/sql 10 | -------------------------------------------------------------------------------- /docs/union.md: -------------------------------------------------------------------------------- 1 | # Rassembler des données de plusieurs tables 2 | 3 | La clause `UNION` peut être utilisée pour regrouper les données de sources différentes dans une même table. Le `UNION ALL` fait la même choses, mais sans réaliser de dédoublonnement, ce qui est plus rapide. 4 | 5 | **Rassembler les routes et les chemins** ensemble, en ajoutant un champ "nature" pour les différencier 6 | 7 | ```sql 8 | -- Rassembler des données de tables différentes 9 | -- On utilise une UNION ALL 10 | 11 | (SELECT 'chemin' AS nature, 12 | geom, 13 | ROUND(ST_LENGTH(geom))::integer AS longueur 14 | FROM z_formation.chemin 15 | LIMIT 100) 16 | -- UNION ALL est placé entre 2 SELECT 17 | UNION ALL 18 | (SELECT 'route' AS nature, 19 | geom, 20 | ROUND(ST_LENGTH(geom))::integer AS longueur 21 | FROM z_formation.route 22 | LIMIT 100) 23 | -- Le ORDER BY doit être réalisé à la fin, et non sur chaque SELECT 24 | ORDER BY longueur 25 | ``` 26 | 27 | Si on doit réaliser le même calcul sur chaque sous-ensemble (chaque SELECT), on peut le faire en 2 étapes via une sous-requête (ou une clause WITH) 28 | 29 | ```sql 30 | SELECT 31 | -- on récupère tous les champs 32 | source.*, 33 | -- on calcule la longueur après rassemblement des données 34 | st_length(geom) AS longueur 35 | FROM ( 36 | (SELECT id, geom 37 | FROM z_formation.chemin 38 | LIMIT 100) 39 | UNION ALL 40 | (SELECT id, geom 41 | FROM z_formation.route 42 | LIMIT 100) 43 | ) AS source 44 | ORDER BY longueur DESC 45 | ; 46 | ``` 47 | 48 | Continuer vers [Enregistrer les requêtes: VIEW](./save_queries.md) 49 | -------------------------------------------------------------------------------- /docs/utils.md: -------------------------------------------------------------------------------- 1 | # Fonctions utiles 2 | 3 | Nous regroupons ici quelques fonctions réalisées au cours de formations ou d'accompagnements d'utilisateurs de PostgreSQL. 4 | 5 | ## Ajout de l'auto-incrémentation sur un champ entier 6 | 7 | Lorsqu'on importe une couche dans une table via les outils de QGIS, 8 | le champ d'identifiant choisi n'a pas le support de l'auto-incrémentation, 9 | ce qui peut poser des problèmes de l'ajout de nouvelles données. 10 | 11 | Depuis PostgreSQL 10, on peut maintenant utiliser des **identités** 12 | au lieu des **serial** pour avoir un champ auto-complété. 13 | Voir par exemple l'article https://www.loxodata.com/post/identity/ 14 | 15 | Pour ajouter le support de l'auto-incrémentation sur un champ entier 16 | à une table existante, on peut utiliser les commandes suivantes : 17 | 18 | ```sql 19 | -- Activer la génération automatique 20 | ALTER TABLE "monschema"."test" ALTER "id" ADD GENERATED BY DEFAULT AS IDENTITY; 21 | 22 | -- Mettre la valeur de la séquence (implicite et cachée) à la valeur max du champ d'identifiant 23 | SELECT setval(pg_get_serial_sequence('"monschema"."test"', 'id'), (SELECT max("id") FROM "monschema"."test")); 24 | ``` 25 | 26 | Pour transformer les séquences créées précédemment via des `serial` en identité avec `identity`, on peut lancer : 27 | 28 | ```sql 29 | -- Enlever la valeur par défaut sur le champ d'identifiant 30 | ALTER TABLE "monschema"."test" ALTER COLUMN id DROP DEFAULT; 31 | 32 | -- Supprimer la séquence 33 | DROP SEQUENCE IF EXISTS "monschema"."test_id_seq"; 34 | 35 | -- Activer la génération automatique 36 | ALTER TABLE "monschema"."test" ALTER "id" ADD GENERATED BY DEFAULT AS IDENTITY; 37 | 38 | -- Mettre la valeur de la séquence (implicite et cachée) à la valeur max du champ d'identifiant 39 | SELECT setval(pg_get_serial_sequence('"monschema"."test"', 'id'), (SELECT max("id") FROM "monschema"."test")); 40 | ``` 41 | 42 | 43 | ## Création automatique d'indexes spatiaux 44 | 45 | Pour des données spatiales volumineuses, les performances d'affichage sont bien meilleures 46 | à grande échelle si on a ajouté un **index spatial**. L'index est aussi beaucoup utilisé 47 | pour améliorer les performances d'analyses spatiales. 48 | 49 | On peut créer l'index spatial table par table, ou bien automatiser cette création, 50 | c'est-à-dire créer les indexes spatiaux **pour toutes les tables qui n'en ont pas**. 51 | 52 | Pour cela, nous avons conçu une fonction, téléchargeable ici: https://gist.github.com/mdouchin/cfa0e37058bcf102ed490bc59d762042 53 | 54 | On doit copier/coller le script SQL de cette page `GIST` dans la **fenêtre SQL** 55 | du Gestionnaire de bases de données de QGIS, puis lancer la requête avec **Exécuter**. 56 | On peut ensuite vider le contenu de la fenêtre, puis appeler la fonction `create_missing_spatial_indexes` via le code SQL suivant : 57 | 58 | ```sql 59 | -- On lance avec le paramètre à True si on veut juste voir les tables qui n'ont pas d'index spatial 60 | -- On lance avec False si on veut créer les indexes automatiquement 61 | 62 | -- Vérification 63 | SELECT * FROM create_missing_spatial_indexes( True ); 64 | 65 | -- Création 66 | SELECT * FROM create_missing_spatial_indexes( False ); 67 | ``` 68 | 69 | ## Trouver toutes les tables sans clé primaire 70 | 71 | Il est très important de déclarer une clé primaire pour vos tables stockées dans PostgreSQL. Cela fournit un moyen aux logiciels comme QGIS d'identifier de manière performante les lignes dans une table. Sans clé primaire, les performances d'accès aux données peuvent être dégradées. 72 | 73 | Vous pouvez trouver l'ensemble des tables de votre base de données sans clé primaire en construisant cette vue PostgreSQL `tables_without_primary_key`: 74 | 75 | ```sql 76 | DROP VIEW IF EXISTS tables_without_primary_key; 77 | CREATE VIEW tables_without_primary_key AS 78 | SELECT t.table_schema, t.table_name 79 | FROM information_schema.tables AS t 80 | LEFT JOIN information_schema.table_constraints AS c 81 | ON t.table_schema = c.table_schema 82 | AND t.table_name = c.table_name 83 | AND c.constraint_type = 'PRIMARY KEY' 84 | WHERE True 85 | AND t.table_type = 'BASE TABLE' 86 | AND t.table_schema not in ('pg_catalog', 'information_schema') 87 | AND c.constraint_name IS NULL 88 | ORDER BY table_schema, table_name 89 | ; 90 | ``` 91 | 92 | * Pour lister les tables sans clé primaire, vous pouvez ensuite lancer la requête suivante: 93 | 94 | ```sql 95 | SELECT * 96 | FROM tables_without_primary_key; 97 | ``` 98 | 99 | Ce qui peut donner par exemple: 100 | 101 | | table_schema | table_name | 102 | |---------------|----------------| 103 | | agriculture | parcelles | 104 | | agriculture | puits | 105 | | cadastre | sections | 106 | | environnement | znieff | 107 | | environnement | parcs_naturels | 108 | 109 | 110 | * Pour lister les tables sans clé primaire dans un schéma particulier, par exemple `cadastre`, vous pouvez ensuite lancer la requête : 111 | 112 | ```sql 113 | SELECT * 114 | FROM tables_without_primary_key 115 | WHERE table_schema IN ('cadastre'); 116 | ``` 117 | 118 | Ce qui peut alors donner: 119 | 120 | | table_schema | table_name | 121 | |---------------|----------------| 122 | | cadastre | sections | 123 | 124 | 125 | 126 | ## Ajouter automatiquement plusieurs champs à plusieurs tables 127 | 128 | Il est parfois nécessaire d'**ajouter des champs à une ou plusieurs tables**, par exemple pour y stocker ensuite des métadonnées (date de modification, date d'ajout, utilisateur, lien, etc). 129 | 130 | Nous proposons pour cela la fonction `ajout_champs_dynamiques` qui permet de fournir un nom de schéma, un nom de table, et une chaîne de caractère contenant la liste séparée par virgule des champs et de leur type. 131 | 132 | La fonction est accessible ici: https://gist.github.com/mdouchin/50234f1f33801aed6f4f2cbab9f4887c 133 | 134 | * Exemple d'utilisation **pour une table** `commune` du schéma `test`: on ajoute les champs `date_creation`, `date_modification` et `utilisateur` 135 | 136 | ```sql 137 | SELECT 138 | ajout_champs_dynamiques('test', 'commune', 'date_creation timestamp DEFAULT now(), date_modification timestamp DEFAULT now(), utilisateur text') 139 | ; 140 | ``` 141 | 142 | * Exemple d'utilisation pour **toutes les tables d'un schéma**, ici le schéma `test`. On utilise dans cette exemple la vue `geometry_columns` qui liste les tables spatiales, car on souhaite aussi ne faire cet ajout que pour les données de type **POINT** 143 | 144 | ```sql 145 | -- Lancer la création de champs sur toutes les tables 146 | -- du schéma test 147 | -- contenant des géométries de type Point 148 | SELECT f_table_schema, f_table_name, 149 | ajout_champs_dynamiques( 150 | -- schéma 151 | f_table_schema, 152 | -- table 153 | f_table_name, 154 | -- liste des champs, au format nom_du_champ TYPE 155 | 'date_creation timestamp DEFAULT now(), date_modification timestamp DEFAULT now(), utilisateur text' 156 | ) 157 | FROM geometry_columns 158 | WHERE True 159 | AND "type" LIKE '%POINT' 160 | AND f_table_schema IN ('test') 161 | ORDER BY f_table_schema, f_table_name 162 | ; 163 | ``` 164 | 165 | ## Vérifier la taille des bases, tables et schémas 166 | 167 | ### Connaître la taille des bases de données 168 | 169 | On peut lancer la requête suivante, qui renvoie les bases de données ordonnées par taille descendante. 170 | 171 | ```sql 172 | SELECT 173 | pg_database.datname AS db_name, 174 | pg_database_size(pg_database.datname) AS db_size, 175 | pg_size_pretty(pg_database_size(pg_database.datname)) AS db_pretty_size 176 | FROM pg_database 177 | WHERE datname NOT IN ('postgres', 'template0', 'template1') 178 | ORDER BY db_size DESC; 179 | ``` 180 | 181 | ### Calculer la taille des tables 182 | 183 | On crée une fonction `get_table_info` qui utilise les tables système pour lister les tables, récupérer leur schéma et les informations de taille. 184 | 185 | ```sql 186 | DROP FUNCTION IF EXISTS get_table_info(); 187 | CREATE OR REPLACE FUNCTION get_table_info() 188 | RETURNS TABLE ( 189 | oid oid, 190 | schema_name text, 191 | table_name text, 192 | row_count integer, 193 | total_size bigint, 194 | pretty_total_size text 195 | ) 196 | AS $$ 197 | BEGIN 198 | RETURN QUERY 199 | SELECT 200 | b.oid, b.schema_name::text, b.table_name::text, 201 | b.row_count::integer, 202 | b.total_size::bigint, 203 | pg_size_pretty(b.total_size) AS pretty_total_size 204 | FROM ( 205 | SELECT *, 206 | a.total_size - index_bytes - COALESCE(toast_bytes,0) AS table_bytes 207 | FROM ( 208 | SELECT 209 | c.oid, 210 | nspname AS schema_name, 211 | relname AS TABLE_NAME, 212 | c.reltuples AS row_count, 213 | pg_total_relation_size(c.oid) AS total_size, 214 | pg_indexes_size(c.oid) AS index_bytes, 215 | pg_total_relation_size(reltoastrelid) AS toast_bytes 216 | FROM pg_class c 217 | LEFT JOIN pg_namespace n 218 | ON n.oid = c.relnamespace 219 | WHERE relkind = 'r' 220 | AND nspname NOT IN ('pg_catalog', 'information_schema') 221 | ) AS a 222 | ) AS b 223 | ; 224 | END; $$ 225 | LANGUAGE 'plpgsql'; 226 | ``` 227 | 228 | On peut l'utiliser simplement de la manière suivante 229 | 230 | ```sql 231 | -- Liste les tables 232 | SELECT * FROM get_table_info() ORDER BY schema_name, table_name DESC; 233 | 234 | -- Lister les tables dans l'ordre inverse de taille 235 | SELECT * FROM get_table_info() ORDER BY total_size DESC; 236 | 237 | ``` 238 | 239 | ### Calculer la taille des schémas 240 | 241 | On crée une simple fonction qui renvoie la somme des tailles des tables d'un schéma 242 | 243 | ```sql 244 | -- Fonction pour calculer la taille d'un schéma 245 | CREATE OR REPLACE FUNCTION pg_schema_size(schema_name text) 246 | RETURNS BIGINT AS 247 | $$ 248 | SELECT 249 | SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename)))::BIGINT 250 | FROM pg_tables 251 | WHERE schemaname = schema_name 252 | $$ 253 | LANGUAGE SQL; 254 | ``` 255 | 256 | On peut alors l'utiliser pour connaître la taille d'un schéma 257 | 258 | ```sql 259 | -- utilisation pour un schéma 260 | SELECT pg_size_pretty(pg_schema_size('public')) AS ; 261 | ``` 262 | 263 | Ou lister l'ensemble des schémas 264 | 265 | ```sql 266 | -- lister les schémas et récupérer leur taille 267 | SELECT schema_name, pg_size_pretty(pg_schema_size(schema_name)) 268 | FROM information_schema.schemata 269 | WHERE schema_name NOT IN ('pg_catalog', 'information_schema') 270 | ORDER BY pg_schema_size(schema_name) DESC; 271 | ``` 272 | 273 | ## Lister les triggers appliqués sur les tables 274 | 275 | On peut utiliser la requête suivante pour lister l'ensemble des triggers activés sur les tables 276 | 277 | ```sql 278 | SELECT 279 | event_object_schema AS table_schema, 280 | event_object_table AS table_name, 281 | trigger_schema, 282 | trigger_name, 283 | string_agg(event_manipulation, ',') AS event, 284 | action_timing AS activation, 285 | action_condition AS condition, 286 | CASE WHEN tgenabled = 'O' THEN True ELSE False END AS trigger_active, 287 | action_statement AS definition 288 | FROM information_schema.triggers AS t 289 | INNER JOIN pg_trigger AS p 290 | ON p.tgrelid = concat('"', event_object_schema, '"."', event_object_table, '"')::regclass 291 | AND trigger_name = tgname 292 | WHERE True 293 | GROUP BY 1,2,3,4,6,7,8,9 294 | ORDER BY table_schema, table_name 295 | ; 296 | ``` 297 | 298 | Cette requête renvoie un tableau de la forme : 299 | 300 | | table_schema | table_name | trigger_schema | trigger_name | event | activation | condition | trigger_active | definition | 301 | |--------------|------------------------|----------------|----------------------|--------|------------|-----------|--------------- |------------------------------------------------------| 302 | | gestion | acteur | gestion | tr_date_maj | UPDATE | BEFORE | | f | EXECUTE FUNCTION occtax.maj_date() | 303 | | occtax | organisme | occtax | tr_date_maj | UPDATE | BEFORE | | t | EXECUTE FUNCTION occtax.maj_date() | 304 | | taxon | iso_metadata_reference | taxon | update_imr_timestamp | UPDATE | BEFORE | | t | EXECUTE FUNCTION taxon.update_imr_timestamp_column() | 305 | 306 | 307 | ## Lister les fonctions installées par les extensions 308 | 309 | Il est parfois utile de lister les **fonctions des extensions**, par exemple pour : 310 | 311 | * vérifier leur nom et leurs paramètres. 312 | * détecter celles qui n'ont pas le bon propriétaire 313 | 314 | La requête suivante permet d'afficher les informations essentielles des fonctions créées 315 | par les extensions installées dans la base : 316 | 317 | ```sql 318 | SELECT DISTINCT 319 | ne.nspname AS extension_schema, 320 | e.extname AS extension_name, 321 | np.nspname AS function_schema, 322 | p.proname AS function_name, 323 | pg_get_function_identity_arguments(p.oid) AS function_params, 324 | proowner::regrole AS function_owner 325 | FROM 326 | pg_catalog.pg_extension AS e 327 | INNER JOIN pg_catalog.pg_depend AS d ON (d.refobjid = e.oid) 328 | INNER JOIN pg_catalog.pg_proc AS p ON (p.oid = d.objid) 329 | INNER JOIN pg_catalog.pg_namespace AS ne ON (ne.oid = e.extnamespace) 330 | INNER JOIN pg_catalog.pg_namespace AS np ON (np.oid = p.pronamespace) 331 | WHERE 332 | TRUE 333 | -- only extensions 334 | AND d.deptype = 'e' 335 | -- not in pg_catalog 336 | AND ne.nspname NOT IN ('pg_catalog') 337 | -- optionnally filter some extensions 338 | -- AND e.extname IN ('postgis', 'postgis_raster') 339 | -- optionnally filter by some owner 340 | AND proowner::regrole::text IN ('postgres') 341 | ORDER BY 342 | extension_name, 343 | function_name; 344 | ; 345 | ``` 346 | 347 | qui renvoie une résultat comme ceci (cet exemple est un extrait de quelques lignes) : 348 | 349 | 350 | | extension_schema | extension_name | function_schema | function_name | function_params | function_owner | 351 | |-------------------|----------------|-----------------|-----------------------------|------------------------------------------------------|-----------------| 352 | | public | fuzzystrmatch | public | levenshtein_less_equal | text, text, integer | johndoe | 353 | | public | fuzzystrmatch | public | metaphone | text, integer | johndoe | 354 | | public | fuzzystrmatch | public | soundex | text | johndoe | 355 | | public | fuzzystrmatch | public | text_soundex | text | johndoe | 356 | | public | hstore | public | akeys | hstore | johndoe | 357 | | public | hstore | public | avals | hstore | johndoe | 358 | | public | hstore | public | defined | hstore, text | johndoe | 359 | | public | postgis | public | st_buffer | text, double precision, integer | johndoe | 360 | | public | postgis | public | st_buffer | geom geometry, radius double precision, options text | johndoe | 361 | | public | postgis | public | st_buildarea | geometry | johndoe | 362 | 363 | On peut bien sûr modifier la clause `WHERE` pour filtrer plus ou moins les fonctions renvoyées. 364 | 365 | 366 | ## Lister les vues contenant `row_number() over()` non typé en `integer` 367 | 368 | Si on utilise des vues dans QGIS qui créent un identifiant unique via le numéro de ligne, il est important : 369 | 370 | * que le type de cet identifiant soit entier `integer` et pas entier long `bigint` 371 | * avoir une clause `ORDER BY` pour essayer au maximum que QGIS récupère les objets toujours dans le même ordre. 372 | 373 | Quand une requête d'une vue utilise `row_number() OVER()`, depuis des versions récentes de PostgreSQL, cela renvoie un entier long `bigint` ce qui n'est pas conseillé. 374 | 375 | On peut trouver ces vues ou vues matérialisées via cette requête : 376 | 377 | ```sql 378 | -- vues 379 | SELECT 380 | concat('"', schemaname, '"."', viewname, '"') AS row_number_view 381 | FROM pg_views 382 | WHERE "definition" ~* '(.)+row_number\(\s*\)\s*over\s*\(\s*\) (.)+' 383 | ORDER BY schemaname, viewname 384 | ; 385 | 386 | -- vues matérialisées 387 | SELECT 388 | concat('"', schemaname, '"."', matviewname, '"') AS row_number_view 389 | FROM pg_views 390 | WHERE "definition" ~* '(.)+row_number\(\s*\)\s*over\s*\(\s*\) (.)+' 391 | ORDER BY schemaname, matviewname 392 | ; 393 | ``` 394 | 395 | ## Lister les tables qui ont une clé primaire non entière 396 | 397 | Pour éviter des soucis de performances sur les gros jeux de données, il faut éviter d'avoir des tables avec des clés primaires sur des champs qui ne sont pas de type entier `integer`. 398 | 399 | En effet, dans QGIS, l'ouverture de ce type de table avec une clé primaire de type `text`, ou même `bigint`, cela entraîne la création et le stockage en mémoire d'une table de correspondance entre chaque objet de la couche et le numéro d'arrivée de la ligne. Sur les tables volumineuses, cela peut être sensible. 400 | 401 | Pour trouver toutes les tables, on peut faire cette requête : 402 | 403 | ```sql 404 | SELECT 405 | nspname AS table_schema, relname AS table_name, 406 | a.attname AS column_name, 407 | format_type(a.atttypid, a.atttypmod) AS column_type 408 | FROM pg_index AS i 409 | JOIN pg_class AS c 410 | ON i.indrelid = c.oid 411 | JOIN pg_attribute AS a 412 | ON a.attrelid = c.oid 413 | AND a.attnum = any(i.indkey) 414 | JOIN pg_namespace AS n 415 | ON n.oid = c.relnamespace 416 | WHERE indisprimary AND nspname NOT LIKE 'pg_%' AND nspname NOT LIKE 'lizmap_%' 417 | AND format_type(a.atttypid, a.atttypmod) != 'integer'; 418 | ``` 419 | 420 | Ce qui donne par exemple : 421 | 422 | table_schema | table_name | column_name | column_type 423 | -------------------|----------------------------------|-------------|------------------- 424 | un_schema | une_table_a | id | bigint 425 | un_schema | une_table_b | id | bigint 426 | un_autre_schema | autre_table_c | id | character varying 427 | un_autre_schema | autre_table_d | id | character varying 428 | 429 | 430 | ## Trouver les tables spatiales avec une géométrie non typée 431 | 432 | Il est important lorsqu'on crée des champs de type géométrie `geometry` de préciser le type des objets (point, ligne, polygone, etc.) et la projection. 433 | 434 | On doit donc créer les champs comme ceci : 435 | 436 | ```sql 437 | CREATE TABLE test ( 438 | id serial primary key, 439 | geom geometry(Point, 2154) 440 | ); 441 | ``` 442 | 443 | et non comme ceci : 444 | 445 | ```sql 446 | CREATE TABLE test ( 447 | id serial primary key, 448 | geom geometry 449 | ); 450 | ``` 451 | 452 | C'est donc important lorsqu'on crée des tables à partir de requêtes SQL de toujours bien typer les géométries. Par exemple : 453 | 454 | ```sql 455 | CREATE TABLE test AS 456 | SELECT id, 457 | ST_Centroid(geom)::geometry(Point, 2154) AS geom 458 | -- ne pas faire : 459 | -- ST_Centroid(geom) AS geom 460 | FROM autre_table 461 | ``` 462 | 463 | On peut trouver toutes les tables qui auraient été créées avec des champs de géométrie non typés via la requête suivante : 464 | 465 | ```sql 466 | SELECT * 467 | FROM geometry_columns 468 | WHERE srid = 0 OR lower(type) = 'geometry' 469 | ; 470 | ``` 471 | 472 | Il faut corriger ces vues ou tables. 473 | 474 | ## Trouver les objets avec des géométries trop complexes 475 | 476 | ```sql 477 | SELECT count(*) 478 | FROM ma_table 479 | WHERE ST_NPoints(geom) > 10000 480 | ; 481 | ``` 482 | 483 | Les trop gros polygones (zones inondables, zonages issus de regroupement de nombreux objets, etc.) peuvent poser de réels soucis de performance, notamment sur les opérations d'intersection avec les objets d'autres couches via `ST_Intersects`. 484 | 485 | On peut corriger cela via la fonction `ST_Subdivide`. Voir [Documentation de ST_Subdivide](https://postgis.net/docs/ST_Subdivide.html) 486 | 487 | 488 | ## Tester les différences entre 2 tables de même structure 489 | 490 | Nous souhaitons **comparer deux tables de la base**, par exemple une table de communes en 2021 `communes_2021` et une table de communes en 2022 `communes_2022`. 491 | 492 | On peut utiliser une fonction qui utilise les possibilités du format hstore pour comparer les données entre elles. 493 | 494 | ```sql 495 | -- On ajoute le support du format hstore 496 | CREATE EXTENSION IF NOT EXISTS hstore; 497 | 498 | -- On crée la fonction de comparaison 499 | DROP FUNCTION compare_tables(text,text,text,text,text,text[]); 500 | CREATE OR REPLACE FUNCTION compare_tables( 501 | p_schema_name_a text, 502 | p_table_name_a text, 503 | p_schema_name_b text, 504 | p_table_name_b text, 505 | p_common_identifier_field text, 506 | p_excluded_fields text[] 507 | 508 | ) RETURNS TABLE( 509 | uid text, 510 | status text, 511 | table_a_values hstore, 512 | table_b_values hstore 513 | ) 514 | LANGUAGE plpgsql 515 | AS $_$ 516 | DECLARE 517 | sqltemplate text; 518 | BEGIN 519 | 520 | -- Compare data 521 | sqltemplate = ' 522 | SELECT 523 | coalesce(ta."%1$s", tb."%1$s") AS "%1$s", 524 | CASE 525 | WHEN ta."%1$s" IS NULL THEN ''not in table A'' 526 | WHEN tb."%1$s" IS NULL THEN ''not in table B'' 527 | ELSE ''table A != table B'' 528 | END AS status, 529 | CASE 530 | WHEN ta."%1$s" IS NULL THEN NULL 531 | ELSE (hstore(ta.*) - ''%6$s''::text[]) - (hstore(tb) - ''%6$s''::text[]) 532 | END AS values_in_table_a, 533 | CASE 534 | WHEN tb."%1$s" IS NULL THEN NULL 535 | ELSE (hstore(tb.*) - ''%6$s''::text[]) - (hstore(ta) - ''%6$s''::text[]) 536 | END AS values_in_table_b 537 | FROM "%2$s"."%3$s" AS ta 538 | FULL JOIN "%4$s"."%5$s" AS tb 539 | ON ta."%1$s" = tb."%1$s" 540 | WHERE 541 | (hstore(ta.*) - ''%6$s''::text[]) != (hstore(tb.*) - ''%6$s''::text[]) 542 | OR (ta."%1$s" IS NULL) 543 | OR (tb."%1$s" IS NULL) 544 | '; 545 | 546 | RETURN QUERY 547 | EXECUTE format(sqltemplate, 548 | p_common_identifier_field, 549 | p_schema_name_a, 550 | p_table_name_a, 551 | p_schema_name_b, 552 | p_table_name_b, 553 | p_excluded_fields 554 | ); 555 | 556 | END; 557 | $_$; 558 | ``` 559 | 560 | Cette fonction attend en paramètres 561 | 562 | * le schéma de la **table A**. Ex: `referentiels` 563 | * le nom de la **table A**. Ex: `communes_2021` 564 | * le schéma de la **table B**. Ex: `referentiels` 565 | * le nom de la **table B**. Ex: `communes_2022` 566 | * le nom du champ qui identifie de manière unique la donnée. Ce n'est pas forcément la clé primaire. Ex `code_commune` 567 | * un tableau de champs pour lesquels ne pas vérifier les différences. Ex: `array['region', 'departement']` 568 | 569 | La requête à lancer est la suivantes 570 | ```sql 571 | SELECT "uid", "status", "table_a_values", "table_b_values" 572 | FROM compare_tables( 573 | 'referentiels', 'commune_2021', 574 | 'referentiels', 'commune_2022', 575 | 'code_commune', 576 | array['region', 'departement'] 577 | ) 578 | ORDER BY status, uid 579 | ; 580 | ``` 581 | 582 | Exemple de données renvoyées: 583 | 584 | | uid | status | table_a_values | table_b_values | 585 | |-------|--------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------| 586 | | 12345 | not in table A | NULL | "annee_ref"=>"2022", "nom_commune"=>"Nouvelle commune", "population"=>"5723" | 587 | | 97612 | not in table B | "annee_ref"=>"2021", "nom_commune"=>"Ancienne commune", "population"=>"840" | NULL | 588 | | 97602 | table A != table B | "annee_ref"=>"2021", "population"=>"1245" | "annee_ref"=>"2022", "population"=>"1322" | 589 | 590 | Dans l'affichage ci-dessus, je n'ai pas affiché le champ de géométrie, mais la fonction teste aussi les différences de géométries. 591 | 592 | *Attention, les performances de ce type de requête ne sont pas forcément assurées pour des volumes de données importants.* 593 | 594 | 595 | ## Trouver les valeurs distinctes des champs d'une table 596 | 597 | Pour comprendre quelles données sont présentes dans une table PostgreSQL, 598 | vous pouvez exploiter la puissance des fonctions de manipulation du `JSON` 599 | et récupérer automatiquement toutes les **valeurs distinctes** d'une table. 600 | 601 | Cela permet de lister les **champs** de cette table et de bien se représenter 602 | ce qu'ils contiennent. 603 | 604 | ```sql 605 | SELECT 606 | -- nom du champ de la table 607 | key AS champ, 608 | 609 | -- On regroupe les valeurs distinctes du champ 610 | -- depuis le JSON calculé plus bas via to_jsonb 611 | -- On compte les valeurs distinctes 612 | count(DISTINCT value) AS nombre, 613 | 614 | -- On récupère les valeurs uniques pour ce champ 615 | json_agg(DISTINCT value) AS valeurs 616 | FROM 617 | -- Table dans laquelle chercher les valeurs uniques 618 | velo.amenagement AS i, 619 | -- Transformation de chaque ligne de la table en JSON (paires clé/valeurs) 620 | jsonb_each( 621 | -- on utilise le - 'id' - 'geom' pour ne pas récupérer les valeurs de ces champs 622 | to_jsonb(i) - 'id' - 'geom' 623 | ) 624 | -- On regroupe par clé, c'est-à-dire par champ 625 | GROUP BY key; 626 | ``` 627 | 628 | ce qui donnera comme résultat 629 | 630 | ```sql 631 | champ | nombre | valeurs 632 | ------------+--------+-------------------------------------------------------------------------------------------------------- 633 | commune | 8 | ["AMBON", "ARZAL", "BILLIERS", "LA ROCHE-BERNARD", "LE GUERNO", "MUZILLAC", "NIVILLAC", "SAINT-DOLAY"] 634 | gestionnai | 3 | ["Commune", "Département", "EPCI"] 635 | id_iti | 9 | ["iti_02", "iti_03", "iti_06", "iti_07", "iti_08", "iti_09", "iti_13", "iti_15", "iti_18"] 636 | insee | 9 | ["56002", "56004", "56018", "56077", "56143", "56147", "56149", "56195", "56212"] 637 | maitre_ouv | 3 | ["Commune", "Département", "EPCI"] 638 | rlv_chauss | 5 | ["Double sens", "Interdit à la circ.", "NC", "Rond-point", "Sens unique"] 639 | rlv_md_dx_ | 5 | ["Aucun aménagement", "Bande", "Contresens cyclable", "Voie uniquement piétonne", "Voie verte"] 640 | rlv_pente | 5 | ["Forte (ponctuelle)", "Forte (tronçon)", "Moyenne", "NC", "Nulle ou faible"] 641 | rlv_vitess | 7 | ["< 20", "20", "30", "50", "70", "80 et plus", "NC"] 642 | type_surfa | 3 | ["Lisse", "Meuble", "Rugueux"] 643 | vvv | 3 | ["V3", "V42", "V45"] 644 | ``` 645 | 646 | Points d'attention: 647 | 648 | * Attention aux performances sur un très gros volume de données. 649 | * Bien penser à ne pas prendre en compte les champs qui contiennent 650 | des données différentes pour tous les objets (identifiants, longueur, etc.) 651 | au risque d'avoir une très longue liste de valeurs uniques. 652 | 653 | Continuer vers [Gestion des droits](./grant.md) 654 | -------------------------------------------------------------------------------- /docs/validate_geometries.md: -------------------------------------------------------------------------------- 1 | # Correction des géométries 2 | 3 | Avec PostgreSQL on peut **tester la validité des géométries** d'une table, comprendre la raison et localiser les soucis de validité: 4 | 5 | 6 | ```sql 7 | SELECT 8 | id_parcelle, 9 | -- vérifier si la géom est valide 10 | ST_IsValid(geom) AS validite_geom, 11 | -- connaitre la raison d'invalidité 12 | st_isvalidreason(geom) AS validite_raison, 13 | -- sortir un point qui localise le souci de validité 14 | ST_SetSRID(location(st_isvaliddetail(geom)), 2154) AS geom 15 | FROM z_formation.parcelle_havre 16 | WHERE ST_IsValid(geom) IS FALSE 17 | ``` 18 | 19 | qui renvoie 2 erreurs de polygones croisés. 20 | 21 | | id_parcelle | validite_geom | validite_raison | point_invalide | 22 | |-------------|---------------|------------------------------------------------------|--------------------------------------------| 23 | | 707847 | False | Self-intersection[492016.260004897 6938870.66384629] | 010100000041B93E0AC1071E4122757CAA3D785A41 | 24 | | 742330 | False | Self-intersection[489317.48266784 6939616.89391708] | 0101000000677A40EE95DD1D41FBEF3539F8785A41 | 25 | 26 | et qu'on peut ouvrir comme une nouvelle couche, avec le champ géométrie *point_invalide*, ce qui permet de visualiser dans QGIS les positions des erreurs. 27 | 28 | PostGIS fournir l'outil **ST_MakeValid** pour corriger automatiquement les géométries invalides. On peut l'utiliser pour les lignes et polygones. 29 | 30 | Attention, pour les polygones, cela peut conduire à des géométries de type différent (par exemple une polygone à 2 noeuds devient une ligne). On utilise donc aussi la fonction **ST_CollectionExtract** pour ne récupérer que les polygones. 31 | 32 | ```sql 33 | -- Corriger les géométries 34 | UPDATE z_formation.parcelle_havre 35 | SET geom = ST_Multi(ST_CollectionExtract(ST_MakeValid(geom), 3)) 36 | WHERE NOT ST_isvalid(geom) 37 | 38 | -- Tester 39 | SELECT count(*) 40 | FROM z_formation.parcelle_havre 41 | WHERE NOT ST_isvalid(geom) 42 | ``` 43 | 44 | Il faut aussi supprimer l'ensemble des lignes dans la table qui ne correspondent pas au type de la couche importée. Par exemple, pour les polygones, supprimer les objets dont le nombre de nœuds est inférieur à 3. 45 | 46 | * On les trouve: 47 | 48 | ```sql 49 | SELECT * 50 | FROM z_formation.parcelle_havre 51 | WHERE ST_NPoints(geom) < 3 52 | ``` 53 | 54 | * On les supprime: 55 | 56 | ```sql 57 | DELETE 58 | FROM z_formation.parcelle_havre 59 | WHERE ST_NPoints(geom) < 3 60 | ``` 61 | 62 | 63 | Continuer vers [Vérifier la topologie](./check_topology.md) 64 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Formation PostGIS 2 | site_author: 3Liz 3 | site_description: Formation PostGIS 4 | site_url: https://docs.3liz.org/formation-postgis/ 5 | repo_url: https://github.com/3liz/formation-postgis/ 6 | copyright: '© 3Liz' 7 | 8 | site_dir: build 9 | 10 | nav: 11 | - docs.3liz.org: '../' 12 | - Accueil: index.md 13 | - Liens et données: links_and_data.md 14 | - Gestion des données: postgresql_in_qgis.md 15 | - Import des données: import_data.md 16 | - Sélection: sql_select.md 17 | - Calcul & Fonctions: perform_calculation.md 18 | - Filtrer: filter_data.md 19 | - Regrouper: group_data.md 20 | - Rassembler: union.md 21 | - Enregistrer: save_queries.md 22 | - Jointures: join_data.md 23 | - Fusionner: merge_geometries.md 24 | - Triggers: triggers.md 25 | - Correction géométries: validate_geometries.md 26 | - Topologie: check_topology.md 27 | - Fonctions utiles: utils.md 28 | - Droits: grant.md 29 | - Données externes: fdw.md 30 | - Tutoriels en ligne: tutoriel.md 31 | 32 | plugins: 33 | - search 34 | - git-revision-date-localized 35 | 36 | markdown_extensions: 37 | - toc: 38 | permalink: "#" 39 | - meta: 40 | - pymdownx.highlight: 41 | linenums: true 42 | linenums_style: pymdownx.inline 43 | - pymdownx.superfences: 44 | custom_fences: 45 | - name: mermaid 46 | class: mermaid 47 | format: !!python/name:pymdownx.superfences.fence_div_format 48 | - pymdownx.tabbed: 49 | alternate_style: true 50 | - pymdownx.magiclink: 51 | - pymdownx.tasklist: 52 | - pymdownx.snippets: 53 | - pymdownx.keys: 54 | - attr_list: 55 | - admonition: 56 | - pymdownx.details: 57 | 58 | extra_javascript: 59 | - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js 60 | - https://unpkg.com/mermaid@8.6.4/dist/mermaid.min.js 61 | extra_css: 62 | - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css 63 | 64 | theme: 65 | name: material 66 | font: false 67 | icon: 68 | repo: fontawesome/brands/github-alt 69 | language: 'en' 70 | logo: logo.svg 71 | favicon: logo.svg 72 | palette: 73 | accent: deep-orange 74 | primary: green 75 | scheme: default 76 | features: 77 | # - navigation.tabs 78 | - navigation.tabs.sticky 79 | - navigation.top 80 | - content.code.copy 81 | 82 | extra: 83 | social: 84 | - icon: fontawesome/brands/twitter 85 | link: https://twitter.com/3LIZ_news 86 | 87 | - icon: fontawesome/brands/linkedin 88 | link: https://www.linkedin.com/company/3liz 89 | 90 | - icon: fontawesome/brands/github 91 | link: https://github.com/3liz/ 92 | 93 | - icon: fontawesome/brands/docker 94 | link: https://hub.docker.com/u/3liz 95 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material>=7.0.0,<10.0.0 2 | mkdocs-git-revision-date-localized-plugin 3 | --------------------------------------------------------------------------------