├── README.md ├── Vagrantfile ├── docs ├── css │ ├── custom-hov.css │ ├── custom.css │ ├── highlight.css │ ├── hovercraft.css │ └── impressConsole.css ├── images │ ├── .directory │ ├── apache-architecture.png │ ├── apache.png │ ├── development.png │ ├── docker.svg │ ├── fcgi-summary.svg │ ├── intro.png │ ├── intro.png.map │ ├── intro.svg │ ├── mapproxy.png │ ├── nginx-architecture.png │ ├── nginx.svg │ ├── project-properties.png │ ├── qcoop_flyers.svg │ ├── qcoop_logo.svg │ ├── qcoop_logo_black.svg │ ├── qcooperative.png │ ├── qgis-icon.png │ ├── server-options.png │ ├── system-architecture.png │ ├── system-overview.png.map │ ├── system-overview.svg │ ├── workflow.png.map │ └── workflow.svg ├── index.rst ├── js │ ├── hovercraft.js │ ├── impress.js │ └── impressConsole.js ├── qgis-test-project.qgs ├── run.py └── utils │ ├── __init__.py │ ├── graphviz_directive.py │ ├── graphviz_test.html │ ├── graphviz_test.rst │ ├── graphviz_test.rst.html │ └── images │ └── g.png ├── manual_preparation.sh ├── provisioning ├── apache2.sh ├── common.sh ├── config.sh ├── download_only.sh ├── job.sh ├── mapproxy.sh ├── nginx.sh └── setup.sh └── resources ├── apache2 └── 001-qgis-server.conf ├── mapproxy └── mapproxy.yaml ├── nginx ├── mapproxy ├── qgis-server-fcgi └── qgis-server-python ├── qgis ├── wms-wfs-test-project.qgd └── wms-wfs-test-project.qgs ├── systemd ├── mapproxy@.service ├── qgis-server-fcgi@.service ├── qgis-server-fcgi@.socket └── qgis-server-python@.service ├── uwsgi ├── README.md ├── qgis-server.ini ├── qgis_wrapped_server.py ├── qgis_wrapped_server_wsgi.py └── uwsgi-qgis.service ├── web ├── .directory ├── htdocs │ └── index.html ├── plugins │ ├── ServerSimpleBrowser │ │ ├── LICENSE │ │ ├── README.rst │ │ ├── ServerSimpleBrowser.py │ │ ├── __init__.py │ │ ├── assets │ │ │ ├── getprojectsettings.xsl │ │ │ ├── map_template.html │ │ │ ├── preview.png │ │ │ └── toctree.png │ │ └── metadata.txt │ ├── accesscontrol │ │ ├── __init__.py │ │ ├── accesscontrol.py │ │ └── metadata.txt │ ├── customapi │ │ ├── __init__.py │ │ ├── circle.html │ │ ├── customapi.py │ │ └── metadata.txt │ ├── customservice │ │ ├── __init__.py │ │ ├── customservice.py │ │ └── metadata.txt │ ├── debug │ │ ├── __init__.py │ │ ├── debug.py │ │ └── metadata.txt │ ├── dummycache │ │ ├── __init__.py │ │ ├── dummycache.py │ │ └── metadata.txt │ ├── getfeatureinfo │ │ ├── __init__.py │ │ ├── getfeatureinfo.py │ │ └── metadata.txt │ ├── httpbasic │ │ ├── __init__.py │ │ ├── httpbasic.py │ │ └── metadata.txt │ ├── loadcustomexpressions │ │ ├── __init__.py │ │ ├── loadcustomexpressions.py │ │ └── metadata.txt │ └── xyz │ │ ├── __init__.py │ │ ├── metadata.txt │ │ └── xyz.py └── projects │ ├── .directory │ ├── Readme.txt │ ├── bluemarble.tiff │ ├── helloworld.qgd │ ├── helloworld.qgs │ ├── world.dbf │ ├── world.prj │ ├── world.qix │ ├── world.shp │ └── world.shx └── xvfb └── xvfb.service /README.md: -------------------------------------------------------------------------------- 1 | # Online version of the Workshop presentation 2 | 3 | http://www.itopen.it/bulk/qgis-server-ws/ 4 | 5 | # QGIS Server 3.x Vagrant testing VMs with Apache and Nginx 6 | 7 | Yet another QGIS Server demo VM, initially prepared for Nødebo QGIS 8 | confererence and workshop 2017 this new version uses QGIS 3 9 | and offers new deployment strategies: 10 | 11 | + Apache Fast CGI 12 | + Apache CGI 13 | + Nginx Fast CGI 14 | + Nginx load balancing proxy to Python wsgi 15 | 16 | A mapproxy demo is also installed. 17 | 18 | > Note: this VM was not designed for production but for demonstration purposes only. 19 | 20 | 21 | ## Requirements 22 | 23 | You need a working installation of Vagrant with Virtualbox. 24 | 25 | Please follow the installation instructions here: 26 | 27 | https://www.virtualbox.org/wiki/Downloads 28 | https://www.vagrantup.com/docs/installation/ 29 | 30 | For disk resizing you will also need the Vagrant plugin `vagrant-disksize`, you can install the plugin with: 31 | 32 | ``` 33 | vagrant plugin install vagrant-disksize 34 | ``` 35 | 36 | > Note: if you have any issue installing `vagrant-disksize` plugin on Linux, you can try to upgrade Vagrant with the following command (adapt the version numbers to the newest available release) 37 | 38 | ``` 39 | wget -c https://releases.hashicorp.com/vagrant/2.0.3/vagrant_2.0.3_x86_64.deb 40 | sudo dpkg -i vagrant_2.0.3_x86_64.deb 41 | ``` 42 | 43 | 44 | ## Features 45 | 46 | This machine is based on Ubuntu bionic and comes with a sample project and some sample plugins. 47 | 48 | 49 | | Server | Port | Mapped to | 50 | |--- |--- |--- | 51 | | Nginx FastCGI | 80 | 8080 | 52 | | Apache (Fast)CGI | 81 | 8081 | 53 | | Nginx Python | 82 | 8082 | 54 | | Nginx mapproxy | 83 | 8083 | 55 | 56 | 57 | ### Apache2 (Fast)CGI stack 58 | 59 | - Apache2 60 | - FastCGI with `mod_fcgid` 61 | - CGI with `mod_cgid` 62 | 63 | ### Nginx FastCGI stack 64 | 65 | - Nginx 66 | - systemd 67 | 68 | ### Nginx Python stack 69 | 70 | - Nginx 71 | - Systemd 72 | 73 | ### Plugins 74 | 75 | - HTTP Basic Auth (for WFS protection) 76 | - GetFeatureInfo HTML formatter 77 | - Simple Browser 78 | - XYZ 79 | 80 | ## Documentation 81 | 82 | A presentation is available in the [docs directory](docs/index.rst) 83 | 84 | ## Setup 85 | 86 | From the directory that contains this `README`: 87 | 88 | ``` 89 | vagrant up 90 | ``` 91 | 92 | Follow the steps in the documentation for further setup. 93 | 94 | 95 | ### Details of the provisioning scripts 96 | 97 | The provisioning scripts are contained in the directory `provisioning`. 98 | 99 | The common configuration for all the scripts is in `config.sh`. 100 | 101 | The main script is `setup.sh` which calls all the other scripts. 102 | 103 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | BOX = "bionic-canonical-64" 5 | BOX_URL = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64-vagrant.box" 6 | 7 | 8 | Vagrant.configure("2") do |config| 9 | config.vm.box = BOX 10 | config.vm.box_url = BOX_URL 11 | config.disksize.size = '20GB' 12 | 13 | config.vm.network "forwarded_port", guest: 80, host: 8080 # nginx fastcgi 14 | config.vm.network "forwarded_port", guest: 81, host: 8081 # apache fastcgi 15 | config.vm.network "forwarded_port", guest: 82, host: 8082 # nginx uwsgi 16 | config.vm.network "forwarded_port", guest: 83, host: 8083 # nginx mapproxy 17 | config.vm.network "forwarded_port", guest: 8000, host: 8000 # for qgis_mapserver 18 | 19 | config.vm.provider "virtualbox" do |v| 20 | #v.gui = true 21 | v.memory = 8192 22 | v.cpus = 3 23 | v.customize ["modifyvm", :id, "--vram", "128"] 24 | end 25 | 26 | # Install the required software 27 | config.vm.provision "shell", 28 | path: "provisioning/setup.sh", 29 | args: ENV['SHELL_ARGS'] 30 | 31 | # Run every time the VM starts 32 | config.vm.provision "shell", 33 | path: "provisioning/job.sh", 34 | args: ENV['SHELL_ARGS'], 35 | run: "always" 36 | 37 | end 38 | -------------------------------------------------------------------------------- /docs/css/custom-hov.css: -------------------------------------------------------------------------------- 1 | 2 | /* ABP: custom per singolo tema */ 3 | 4 | 5 | @import url(https://fonts.googleapis.com/css?family=Roboto); 6 | 7 | .warning::before { 8 | content: "!"; 9 | color: white; 10 | background-color: red; 11 | border-radius: 1.5em; 12 | margin-right: 0.5em; 13 | width: 1.4em; 14 | display: inline-block; 15 | align-content: center; 16 | text-align: center; 17 | font-weight: bold; 18 | } 19 | 20 | .step > h1:first-child { 21 | border-bottom: solid 0.2rem green; /*#3CC6B9;*/ 22 | } 23 | 24 | #presentation-title, 25 | #presentation-title h1 { 26 | text-align: center; 27 | border: none; 28 | } 29 | 30 | .centered { 31 | text-align: center; 32 | } 33 | 34 | img.centered { 35 | margin-left: auto; 36 | margin-right: auto; 37 | display: block; 38 | } 39 | 40 | .pull-right { 41 | display: block; 42 | width: 49%; 43 | float: right; 44 | clear: right; 45 | } 46 | 47 | 48 | img.pull-right { 49 | margin-bottom: 1em; 50 | } 51 | 52 | .pull-left { 53 | display: block; 54 | width: 49%; 55 | } 56 | 57 | ul.pull-left, ul.pull-right { 58 | list-style-position: inside; 59 | padding-left: 0; 60 | } 61 | 62 | .zoom-70 { 63 | font-size: 70% !important; 64 | } 65 | 66 | .zoom-80 { 67 | font-size: 80% !important; 68 | } 69 | 70 | .scale-20 { 71 | max-width: 20% !important; 72 | } 73 | 74 | .scale-30 { 75 | max-width: 30% !important; 76 | } 77 | 78 | .scale-40 { 79 | max-width: 40% !important; 80 | } 81 | 82 | .scale-50 { 83 | max-width: 50% !important; 84 | } 85 | 86 | .scale-70 { 87 | max-width: 70% !important; 88 | } 89 | 90 | .scale-80 { 91 | max-width: 80% !important; 92 | } 93 | 94 | .scale-90 { 95 | max-width: 90% !important; 96 | } 97 | 98 | .scale-100 { 99 | max-width: 100% !important; 100 | } 101 | 102 | .scale-100 { 103 | max-width: 100% !important; 104 | } 105 | 106 | .force-200 { 107 | width: 200% !important; 108 | } 109 | 110 | .force-150 { 111 | width: 150% !important; 112 | } 113 | 114 | .step { 115 | width: 960px; 116 | height: 600px; 117 | } 118 | 119 | li > tt { 120 | font-weight: bold; 121 | font-size: 120%; 122 | } 123 | 124 | h1 > tt, 125 | h2 > tt, 126 | p > tt { 127 | font-weight: bold; 128 | color: rgb(29, 126, 0); 129 | } 130 | 131 | body { 132 | background: white; 133 | color: #222; 134 | font-family: 'Roboto', sans-serif; 135 | font-size: 150%; 136 | } 137 | 138 | .step img { 139 | max-height: 75%; 140 | max-width: 80%; 141 | } 142 | 143 | table p { 144 | margin: 0; 145 | padding: 0.5em; 146 | } 147 | 148 | :link, :visited {text-decoration: none;color:green;} 149 | 150 | 151 | table { 152 | border-collapse:collapse; 153 | } 154 | 155 | table th { 156 | border: solid 1px rgb(146, 146, 146); 157 | } 158 | 159 | table td { 160 | border: solid 1px rgb(146, 146, 146); 161 | } 162 | 163 | blockquote { 164 | color: #92CC47; 165 | font-style: italic; 166 | } 167 | 168 | strong { 169 | color: green; 170 | } 171 | 172 | p.sidebar { 173 | display: block; 174 | /*border:medium outset;*/ 175 | clear:right; 176 | float:right; 177 | /*margin:0 0 0.5em 1em;*/ 178 | /*padding:1em;*/ 179 | width:30%; 180 | } 181 | 182 | p.sidebar { 183 | padding: 0 1em 1em 1em; 184 | font-size: 70%; 185 | } 186 | 187 | p.sidebar ul { 188 | font-size: 70%; 189 | margin-top:0; 190 | } 191 | 192 | 193 | p.sidebar p.sidebar-title { 194 | cursor: pointer; 195 | padding: 1em; 196 | font-weight: bold; 197 | font-size: 80%; 198 | margin:0; 199 | color: #23392F; 200 | } 201 | 202 | 203 | #controls #navLinks a { 204 | font-size: 24px; 205 | } 206 | 207 | #controls #navLinks { 208 | height: 60px; 209 | } 210 | 211 | dt { 212 | font-weight:bold; 213 | } 214 | 215 | pre { 216 | white-space: pre-wrap; 217 | } 218 | 219 | 220 | .borderless td, .borderless th{ 221 | border: solid 1px transparent; 222 | } 223 | 224 | div#header, div#footer { 225 | background:none repeat scroll 0 0 #ddd; 226 | font-family:sans-serif; 227 | } 228 | 229 | 230 | 231 | div#footer { 232 | /*border-top: solid 2px green;*/ 233 | } 234 | 235 | em { 236 | color: brown; 237 | /*font-weight: bold;*/ 238 | /*text-shadow: 0 0 3px #FF0000;*/ 239 | } 240 | 241 | img.align-right { 242 | float: right; 243 | } 244 | 245 | img.align-left{ 246 | float: left; 247 | } 248 | 249 | h1 strong {color: inherit} 250 | 251 | .sidebar em 252 | ,.sidebar cite{ 253 | color: #CC0000; 254 | } 255 | 256 | 257 | tt.docutils , 258 | pre.literal-block, pre.doctest-block, 259 | div.sidebar 260 | { 261 | background-color: #f9f2f4; 262 | border-radius: 4px; 263 | color: #c7254e; 264 | } 265 | 266 | blockquote { 267 | color: #338833; 268 | font-style: italic; 269 | } 270 | 271 | .slide h1 { 272 | background-color: #ddd; 273 | margin-right: 50px; 274 | border-radius: 4px; 275 | } 276 | 277 | 278 | .slide ul ul li p { 279 | margin: 0 4px; 280 | } 281 | 282 | 283 | li dt { 284 | font-weight:normal; 285 | } 286 | 287 | ul li { 288 | margin: 0.5em 0; 289 | } 290 | 291 | #qgis-core table th { 292 | text-align: center; 293 | border: solid 1px gray; 294 | } 295 | 296 | #qgis-core table td { 297 | text-align: right; 298 | width: 5em; 299 | border: solid 1px gray; 300 | } 301 | 302 | .slide > h1 { 303 | text-shadow: #aaa 0 2px 3px; 304 | } 305 | 306 | h2 { 307 | color: green; 308 | } -------------------------------------------------------------------------------- /docs/css/custom.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-size: 150%; 4 | } 5 | 6 | em { 7 | color: maroon; 8 | font-weight: bold; 9 | } 10 | 11 | .header img { 12 | width: 6%; 13 | } 14 | 15 | .footer { 16 | position: fixed; 17 | bottom: 1%; 18 | font-size: 60%; 19 | color: darkgrey; 20 | } 21 | 22 | pre { 23 | font-size: 140%; 24 | font-weight: bold; 25 | } -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 3 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 4 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 5 | .highlight .o { color: #666666 } /* Operator */ 6 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 7 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 8 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 9 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 10 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 11 | .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 13 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 15 | .highlight .go { color: #303030 } /* Generic.Output */ 16 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 17 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 21 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 22 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 23 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 24 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 25 | .highlight .kt { color: #902000 } /* Keyword.Type */ 26 | .highlight .m { color: #208050 } /* Literal.Number */ 27 | .highlight .s { color: #4070a0 } /* Literal.String */ 28 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 29 | .highlight .nb { color: #007020 } /* Name.Builtin */ 30 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 31 | .highlight .no { color: #60add5 } /* Name.Constant */ 32 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 33 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 34 | .highlight .ne { color: #007020 } /* Name.Exception */ 35 | .highlight .nf { color: #06287e } /* Name.Function */ 36 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 37 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 38 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 39 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 40 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 41 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 43 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 44 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 45 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 46 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 47 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 48 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 49 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 50 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 51 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 52 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 53 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 54 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 55 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 56 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 57 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 58 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 59 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 60 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 61 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/css/hovercraft.css: -------------------------------------------------------------------------------- 1 | /* This file left intentionally (almost) blank 2 | 3 | Landslide always adds a print.css and a screen.css, but they 4 | are not needed in impress.js, so this theme leaves them blank, 5 | except for hiding some things you want to hide. 6 | 7 | You can modify these files in your own fork, or you can add 8 | css-files in the landslide configuration file. 9 | See https://github.com/adamzap/landslide#presentation-configuration 10 | 11 | */ 12 | 13 | .impress-supported .fallback-message, 14 | .step .notes { 15 | display: none; 16 | } 17 | 18 | .step { 19 | width: 800px; 20 | } 21 | 22 | 23 | /* Help popup */ 24 | 25 | #hovercraft-help { 26 | background: none repeat scroll 0 0 rgba(0, 0, 0, 0.7); 27 | color: #EEEEEE; 28 | font-size: 70%; 29 | left: 2em; 30 | bottom: 2em; 31 | width: 28em; 32 | border-radius: 1em; 33 | padding: 1em; 34 | position: fixed; 35 | right: 0; 36 | text-align: center; 37 | z-index: 100; 38 | display: block; 39 | font-family: Verdana, Arial, Sans; 40 | } 41 | 42 | .impress-enabled #hovercraft-help.hide { 43 | display: none; 44 | } 45 | 46 | #hovercraft-help.disabled { 47 | display: none; 48 | } 49 | 50 | /* Slide numbers */ 51 | 52 | div#slide-number 53 | { 54 | bottom: 1em; 55 | font-size: 5vh; 56 | position: absolute; 57 | right: 2.5em; 58 | text-align: right; 59 | } 60 | -------------------------------------------------------------------------------- /docs/css/impressConsole.css: -------------------------------------------------------------------------------- 1 | #impressconsole body { 2 | background-color: rgb(255, 255, 255); 3 | padding: 0; 4 | margin: 0; 5 | font-family: verdana, arial, sans-serif; 6 | font-size: 2vw; 7 | } 8 | 9 | #impressconsole div#console { 10 | position: absolute; 11 | top: 0.5vw; 12 | left: 0.5vw; 13 | right: 0.5vw; 14 | bottom: 3vw; 15 | margin: 0; 16 | } 17 | 18 | #impressconsole div#views, #impressconsole div#notes { 19 | position: absolute; 20 | top: 0; 21 | bottom: 0; 22 | } 23 | 24 | #impressconsole div#views { 25 | left: 0; 26 | right: 50vw; 27 | overflow: hidden; 28 | } 29 | 30 | #impressconsole div#blocker { 31 | position: absolute; 32 | right: 0; 33 | bottom: 0; 34 | } 35 | 36 | #impressconsole div#notes { 37 | left: 50vw; 38 | right: 0; 39 | overflow-x: hidden; 40 | overflow-y: auto; 41 | padding: 0.3ex; 42 | background-color: rgb(255, 255, 255); 43 | border: solid 1px rgb(120, 120, 120); 44 | } 45 | 46 | #impressconsole div#notes .noNotes { 47 | color: rgb(200, 200, 200); 48 | } 49 | 50 | #impressconsole div#notes p { 51 | margin-top: 0; 52 | } 53 | 54 | #impressconsole iframe { 55 | position: absolute; 56 | margin: 0; 57 | padding: 0; 58 | left: 0; 59 | border: solid 1px rgb(120, 120, 120); 60 | } 61 | 62 | #impressconsole iframe#slideView { 63 | top: 0; 64 | width: 49vw; 65 | height: 49vh; 66 | } 67 | 68 | #impressconsole iframe#preView { 69 | opacity: 0.7; 70 | top: 50vh; 71 | width: 30vw; 72 | height: 30vh; 73 | } 74 | 75 | #impressconsole div#controls { 76 | margin: 0; 77 | position: absolute; 78 | bottom: 0.25vw; 79 | left: 0.5vw; 80 | right: 0.5vw; 81 | height: 2.5vw; 82 | background-color: rgb(255, 255, 255); 83 | background-color: rgba(255, 255, 255, 0.6); 84 | } 85 | 86 | #impressconsole div#prev, div#next { 87 | } 88 | 89 | #impressconsole div#prev a, #impressconsole div#next a { 90 | display: block; 91 | border: solid 1px rgb(70, 70, 70); 92 | border-radius: 0.5vw; 93 | font-size: 1.5vw; 94 | padding: 0.25vw; 95 | text-decoration: none; 96 | background-color: rgb(220, 220, 220); 97 | color: rgb(0, 0, 0); 98 | } 99 | 100 | #impressconsole div#prev a:hover, #impressconsole div#next a:hover { 101 | background-color: rgb(245, 245, 245); 102 | } 103 | 104 | #impressconsole div#prev { 105 | float: left; 106 | } 107 | 108 | #impressconsole div#next { 109 | float: right; 110 | } 111 | 112 | #impressconsole div#status { 113 | margin-left: 2em; 114 | margin-right: 2em; 115 | text-align: center; 116 | float: right; 117 | } 118 | 119 | #impressconsole div#clock { 120 | margin-left: 2em; 121 | margin-right: 2em; 122 | text-align: center; 123 | float: left; 124 | } 125 | 126 | #impressconsole div#timer { 127 | margin-left: 2em; 128 | margin-right: 2em; 129 | text-align: center; 130 | float: left; 131 | } 132 | 133 | #impressconsole span.moving { 134 | color: rgb(255, 0, 0); 135 | } 136 | 137 | #impressconsole span.ready { 138 | color: rgb(0, 128, 0); 139 | } 140 | -------------------------------------------------------------------------------- /docs/images/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | PreviewsShown=true 3 | Timestamp=2019,3,1,10,31,33 4 | Version=4 5 | -------------------------------------------------------------------------------- /docs/images/apache-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/apache-architecture.png -------------------------------------------------------------------------------- /docs/images/apache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/apache.png -------------------------------------------------------------------------------- /docs/images/development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/development.png -------------------------------------------------------------------------------- /docs/images/docker.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/images/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/intro.png -------------------------------------------------------------------------------- /docs/images/intro.png.map: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/images/intro.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/intro.svg -------------------------------------------------------------------------------- /docs/images/mapproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/mapproxy.png -------------------------------------------------------------------------------- /docs/images/nginx-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/nginx-architecture.png -------------------------------------------------------------------------------- /docs/images/nginx.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/images/project-properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/project-properties.png -------------------------------------------------------------------------------- /docs/images/qcoop_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 64 | 67 | 70 | Q C 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/images/qcoop_logo_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 70 | 75 | Q C 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/images/qcooperative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/qcooperative.png -------------------------------------------------------------------------------- /docs/images/qgis-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/qgis-icon.png -------------------------------------------------------------------------------- /docs/images/server-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/server-options.png -------------------------------------------------------------------------------- /docs/images/system-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/images/system-architecture.png -------------------------------------------------------------------------------- /docs/images/system-overview.png.map: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/images/workflow.png.map: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/js/hovercraft.js: -------------------------------------------------------------------------------- 1 | // Initialize impress.js 2 | impress().init(); 3 | 4 | // Set up the help-box 5 | var helpdiv = window.document.getElementById('hovercraft-help'); 6 | 7 | if (window.top!=window.self) { 8 | // This is inside an iframe, so don't show the help. 9 | helpdiv.className = "disabled"; 10 | 11 | } else { 12 | // Install a funtion to toggle help on and off. 13 | var help = function() { 14 | if(helpdiv.className == 'hide') 15 | helpdiv.className = 'show'; 16 | else 17 | helpdiv.className = 'hide'; 18 | }; 19 | 20 | // The help is by default shown. Hide it after five seconds. 21 | setTimeout(function () { 22 | var helpdiv = window.document.getElementById('hovercraft-help'); 23 | if(helpdiv.className != 'show') 24 | helpdiv.className = 'hide'; 25 | }, 5000); 26 | } 27 | 28 | 29 | if (impressConsole) { 30 | var impressattrs = document.getElementById('impress').attributes; 31 | var consoleCss = impressattrs['console-css']; 32 | var previewCss = null; 33 | if (impressattrs.hasOwnProperty('preview-css')) { 34 | previewCss = impressattrs['preview-css']; 35 | } 36 | 37 | impressConsole().init(css=consoleCss, cssPreview=previewCss); 38 | 39 | // P to open Console 40 | impressConsole().registerKeyEvent([72], help, window); 41 | 42 | if (impressattrs.hasOwnProperty('auto-console') && impressattrs['auto-console'].value.toLowerCase() === 'true') { 43 | consoleWindow = impressConsole().open(); 44 | } 45 | } 46 | 47 | // Function updating the slide number counter 48 | function update_slide_number(evt) 49 | { 50 | var step = evt.target.attributes['step'].value; 51 | document.getElementById('slide-number').innerText = parseInt(step) + 1; 52 | } 53 | -------------------------------------------------------------------------------- /docs/js/impressConsole.js: -------------------------------------------------------------------------------- 1 | /** 2 | * impressConsole.js 3 | * 4 | * Adds a presenter console to impress.js 5 | * 6 | * MIT Licensed, see license.txt. 7 | * 8 | * Copyright 2012-2017 impress-console contributors (see README.txt) 9 | * 10 | * version: 1.4.1 11 | * 12 | */ 13 | 14 | (function ( document, window ) { 15 | 'use strict'; 16 | 17 | // create Language object depending on browsers language setting 18 | var lang; 19 | switch (navigator.language) { 20 | case 'de': 21 | lang = { 22 | 'noNotes' : '
Keine Notizen hierzu
', 23 | 'restart' : 'Neustart', 24 | 'clickToOpen' : 'Klicken um Sprecherkonsole zu öffnen', 25 | 'prev' : 'zurück', 26 | 'next' : 'weiter', 27 | 'loading' : 'initalisiere', 28 | 'ready' : 'Bereit', 29 | 'moving' : 'in Bewegung', 30 | 'useAMPM' : false, 31 | 'gotoSlideNo': 'Gehe zur Foliennummer' // By Google translate 32 | }; 33 | break; 34 | case 'en': 35 | default : 36 | lang = { 37 | 'noNotes' : '
No notes for this step
', 38 | 'restart' : 'Restart', 39 | 'clickToOpen' : 'Click to open speaker console', 40 | 'prev' : 'Prev', 41 | 'next' : 'Next', 42 | 'loading' : 'Loading', 43 | 'ready' : 'Ready', 44 | 'moving' : 'Moving', 45 | 'useAMPM' : false, 46 | 'gotoSlideNo': 'Go to slide number:' 47 | }; 48 | break; 49 | } 50 | 51 | // Settings to set iframe in speaker console 52 | const preViewDefaultFactor = 0.7; 53 | const preViewMinimumFactor = 0.5; 54 | const preViewGap = 4; 55 | 56 | // This is the default template for the speaker console window 57 | const consoleTemplate = '' + 58 | '' + 59 | '' + 60 | '' + 61 | '
' + 62 | '
' + 63 | '' + 64 | '' + 65 | '
' + 66 | '
' + 67 | '
' + 68 | '
' + 69 | '
' + 70 | '' + 71 | '' + 72 | '
--:--
' + 73 | '
00m 00s
' + 74 | '
{{loading}}
' + 75 | '
' + 76 | ''; 77 | 78 | // Default css location 79 | var cssFile = "css/impressConsole.css"; 80 | 81 | // css for styling iframs on the console 82 | var cssFileIframe = null; 83 | 84 | // All console windows, so that you can call impressConsole() repeatedly. 85 | var allConsoles = {}; 86 | 87 | // Zero padding helper function: 88 | var zeroPad = function(i) { 89 | return (i < 10 ? '0' : '') + i; 90 | }; 91 | 92 | // The console object 93 | var impressConsole = window.impressConsole = function (rootId) { 94 | 95 | rootId = rootId || 'impress'; 96 | 97 | if (allConsoles[rootId]) { 98 | return allConsoles[rootId]; 99 | } 100 | 101 | // root presentation elements 102 | var root = document.getElementById( rootId ); 103 | 104 | var consoleWindow = null; 105 | 106 | var nextStep = function() { 107 | var classes = ""; 108 | var nextElement = document.querySelector('.active'); 109 | // return to parents as long as there is no next sibling 110 | while (!nextElement.nextElementSibling && nextElement.parentNode) { 111 | nextElement = nextElement.parentNode; 112 | } 113 | nextElement = nextElement.nextElementSibling; 114 | while (nextElement) { 115 | classes = nextElement.attributes['class']; 116 | if (classes && classes.value.indexOf('step') !== -1) { 117 | return nextElement; 118 | } 119 | 120 | if (nextElement.firstElementChild) { // first go into deep 121 | nextElement = nextElement.firstElementChild; 122 | } 123 | else { 124 | // go to next sibling or through parents until there is a next sibling 125 | while (!nextElement.nextElementSibling && nextElement.parentNode) { 126 | nextElement = nextElement.parentNode; 127 | } 128 | nextElement = nextElement.nextElementSibling; 129 | } 130 | } 131 | // No next element. Pick the first 132 | return document.querySelector('.step'); 133 | }; 134 | 135 | // Sync the notes to the step 136 | var onStepLeave = function(){ 137 | if(consoleWindow) { 138 | // Set notes to next steps notes. 139 | var newNotes = document.querySelector('.active').querySelector('.notes'); 140 | if (newNotes) { 141 | newNotes = newNotes.innerHTML; 142 | } else { 143 | newNotes = lang.noNotes; 144 | } 145 | consoleWindow.document.getElementById('notes').innerHTML = newNotes; 146 | 147 | // Set the views 148 | var baseURL = document.URL.substring(0, document.URL.search('#/')); 149 | var slideSrc = baseURL + '#' + document.querySelector('.active').id; 150 | var preSrc = baseURL + '#' + nextStep().id; 151 | var slideView = consoleWindow.document.getElementById('slideView'); 152 | // Setting them when they are already set causes glithes in Firefox, so we check first: 153 | if (slideView.src !== slideSrc) { 154 | slideView.src = slideSrc; 155 | } 156 | var preView = consoleWindow.document.getElementById('preView'); 157 | if (preView.src !== preSrc) { 158 | preView.src = preSrc; 159 | } 160 | 161 | consoleWindow.document.getElementById('status').innerHTML = '' + lang.moving + ''; 162 | } 163 | }; 164 | 165 | // Sync the previews to the step 166 | var onStepEnter = function(){ 167 | if(consoleWindow) { 168 | // We do everything here again, because if you stopped the previos step to 169 | // early, the onstepleave trigger is not called for that step, so 170 | // we need this to sync things. 171 | var newNotes = document.querySelector('.active').querySelector('.notes'); 172 | if (newNotes) { 173 | newNotes = newNotes.innerHTML; 174 | } else { 175 | newNotes = lang.noNotes; 176 | } 177 | var notes = consoleWindow.document.getElementById('notes'); 178 | notes.innerHTML = newNotes; 179 | notes.scrollTop = 0; 180 | 181 | // Set the views 182 | var baseURL = document.URL.substring(0, document.URL.search('#/')); 183 | var slideSrc = baseURL + '#' + document.querySelector('.active').id; 184 | var preSrc = baseURL + '#' + nextStep().id; 185 | var slideView = consoleWindow.document.getElementById('slideView'); 186 | // Setting them when they are already set causes glithes in Firefox, so we check first: 187 | if (slideView.src !== slideSrc) { 188 | slideView.src = slideSrc; 189 | } 190 | var preView = consoleWindow.document.getElementById('preView'); 191 | if (preView.src !== preSrc) { 192 | preView.src = preSrc; 193 | } 194 | 195 | consoleWindow.document.getElementById('status').innerHTML = '' + lang.ready + ''; 196 | } 197 | }; 198 | 199 | var spaceHandler = function () { 200 | var notes = consoleWindow.document.getElementById('notes'); 201 | if (notes.scrollTopMax - notes.scrollTop > 20) { 202 | notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8; 203 | } else { 204 | impress().next(); 205 | } 206 | }; 207 | 208 | var timerReset = function () { 209 | consoleWindow.timerStart = new Date(); 210 | }; 211 | 212 | var gotoSlide = function (event) { 213 | var target = event.view.prompt("Enter slide number"); 214 | 215 | if (isNaN(target)) { 216 | var goto_status = impress().goto(target); 217 | } else if (target === null) { 218 | return; 219 | } else { 220 | // goto(0) goes to step-1, so substract 221 | var goto_status = impress().goto(parseInt(target) - 1); 222 | } 223 | if (goto_status === false) { 224 | event.view.alert("Slide not found: '" + target + "'"); 225 | } 226 | }; 227 | 228 | // Show a clock 229 | var clockTick = function () { 230 | var now = new Date(); 231 | var hours = now.getHours(); 232 | var minutes = now.getMinutes(); 233 | var seconds = now.getSeconds(); 234 | var ampm = ''; 235 | 236 | if (lang.useAMPM) { 237 | ampm = ( hours < 12 ) ? 'AM' : 'PM'; 238 | hours = ( hours > 12 ) ? hours - 12 : hours; 239 | hours = ( hours === 0 ) ? 12 : hours; 240 | } 241 | 242 | // Clock 243 | var clockStr = zeroPad(hours) + ':' + zeroPad(minutes) + ':' + zeroPad(seconds) + ' ' + ampm; 244 | consoleWindow.document.getElementById('clock').firstChild.nodeValue = clockStr; 245 | 246 | // Timer 247 | seconds = Math.floor((now - consoleWindow.timerStart) / 1000); 248 | minutes = Math.floor(seconds / 60); 249 | seconds = Math.floor(seconds % 60); 250 | consoleWindow.document.getElementById('timer').firstChild.nodeValue = zeroPad(minutes) + 'm ' + zeroPad(seconds) + 's'; 251 | 252 | if (!consoleWindow.initialized) { 253 | // Nudge the slide windows after load, or they will scrolled wrong on Firefox. 254 | consoleWindow.document.getElementById('slideView').contentWindow.scrollTo(0,0); 255 | consoleWindow.document.getElementById('preView').contentWindow.scrollTo(0,0); 256 | consoleWindow.initialized = true; 257 | } 258 | }; 259 | 260 | var registerKeyEvent = function(keyCodes, handler, wnd) { 261 | if (wnd === undefined) { 262 | wnd = consoleWindow; 263 | } 264 | 265 | // prevent default keydown action when one of supported key is pressed 266 | wnd.document.addEventListener("keydown", function ( event ) { 267 | if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey && keyCodes.indexOf(event.keyCode) !== -1) { 268 | event.preventDefault(); 269 | } 270 | }, false); 271 | 272 | // trigger impress action on keyup 273 | wnd.document.addEventListener("keyup", function ( event ) { 274 | if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey && keyCodes.indexOf(event.keyCode) !== -1) { 275 | handler(event); 276 | event.preventDefault(); 277 | } 278 | }, false); 279 | }; 280 | 281 | var consoleOnLoad = function() { 282 | var slideView = consoleWindow.document.getElementById('slideView'); 283 | var preView = consoleWindow.document.getElementById('preView'); 284 | 285 | // Firefox: 286 | slideView.contentDocument.body.classList.add('impress-console'); 287 | preView.contentDocument.body.classList.add('impress-console'); 288 | if (cssFileIframe !== null) { 289 | slideView.contentDocument.head.insertAdjacentHTML('beforeend', 290 | ''); 291 | preView.contentDocument.head.insertAdjacentHTML('beforeend', 292 | ''); 293 | } 294 | // Chrome: 295 | slideView.addEventListener('load', function() { 296 | slideView.contentDocument.body.classList.add('impress-console'); 297 | if (cssFileIframe !== null) { 298 | slideView.contentDocument.head.insertAdjacentHTML('beforeend', 299 | ''); 300 | } 301 | }); 302 | preView.addEventListener('load', function() { 303 | preView.contentDocument.body.classList.add('impress-console'); 304 | if (cssFileIframe !== null) { 305 | preView.contentDocument.head.insertAdjacentHTML('beforeend', 306 | ''); 307 | } 308 | }); 309 | }; 310 | 311 | var open = function() { 312 | if(top.isconsoleWindow){ 313 | return; 314 | } 315 | 316 | if (consoleWindow && !consoleWindow.closed) { 317 | consoleWindow.focus(); 318 | } else { 319 | consoleWindow = window.open(); 320 | 321 | // if opening failes this may be because the browser prevents this from 322 | // not (or less) interactive JavaScript... 323 | if (consoleWindow == null) { 324 | // ... so I add a button to klick. 325 | // workaround on firefox 326 | var message = document.createElement('div'); 327 | message.id = 'consoleWindowError'; 328 | message.style.position = "fixed"; 329 | message.style.left = 0; 330 | message.style.top = 0; 331 | message.style.right = 0; 332 | message.style.bottom = 0; 333 | message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; 334 | message.innerHTML = ''; 335 | document.body.appendChild(message); 336 | return; 337 | } 338 | 339 | // This sets the window location to the main window location, so css can be loaded: 340 | consoleWindow.document.open(); 341 | // Write the template: 342 | consoleWindow.document.write(consoleTemplate.replace("{{cssFile}}", cssFile).replace(/{{.*?}}/gi, function (x){ return lang[x.substring(2, x.length-2)]; })); 343 | consoleWindow.document.title = 'Speaker Console (' + document.title + ')'; 344 | consoleWindow.impress = window.impress; 345 | // We set this flag so we can d etect it later, to prevent infinite popups. 346 | consoleWindow.isconsoleWindow = true; 347 | // Set the onload function: 348 | consoleWindow.onload = consoleOnLoad; 349 | // Add clock tick 350 | consoleWindow.timerStart = new Date(); 351 | consoleWindow.timerReset = timerReset; 352 | consoleWindow.clockInterval = setInterval('impressConsole("' + rootId + '").clockTick()', 1000 ); 353 | 354 | // keyboard navigation handlers 355 | // 33: pg up, 37: left, 38: up 356 | registerKeyEvent([33, 37, 38], impress().prev); 357 | // 34: pg down, 39: right, 40: down 358 | registerKeyEvent([34, 39, 40], impress().next); 359 | // 32: space 360 | registerKeyEvent([32], spaceHandler); 361 | // 82: R 362 | registerKeyEvent([82], timerReset); 363 | // 71: G 364 | registerKeyEvent([71], gotoSlide); 365 | 366 | // Cleanup 367 | consoleWindow.onbeforeunload = function() { 368 | // I don't know why onunload doesn't work here. 369 | clearInterval(consoleWindow.clockInterval); 370 | }; 371 | 372 | // It will need a little nudge on Firefox, but only after loading: 373 | onStepEnter(); 374 | consoleWindow.initialized = false; 375 | consoleWindow.document.close(); 376 | 377 | //catch any window resize to pass size on 378 | window.onresize = resize; 379 | consoleWindow.onresize = resize; 380 | 381 | return consoleWindow; 382 | } 383 | }; 384 | 385 | var resize = function() { 386 | var slideView = consoleWindow.document.getElementById('slideView'); 387 | var preView = consoleWindow.document.getElementById('preView'); 388 | 389 | // get ratio of presentation 390 | var ratio = window.innerHeight / window.innerWidth; 391 | 392 | // get size available for views 393 | var views = consoleWindow.document.getElementById('views'); 394 | 395 | // slideView may have a border or some padding: 396 | // asuming same border width on both direktions 397 | var delta = slideView.offsetWidth - slideView.clientWidth; 398 | 399 | // set views 400 | var slideViewWidth = (views.clientWidth - delta); 401 | var slideViewHeight = Math.floor(slideViewWidth * ratio); 402 | 403 | var preViewTop = slideViewHeight + preViewGap; 404 | 405 | var preViewWidth = Math.floor(slideViewWidth * preViewDefaultFactor); 406 | var preViewHeight = Math.floor(slideViewHeight * preViewDefaultFactor); 407 | 408 | 409 | // shrink preview to fit into space available 410 | if (views.clientHeight - delta < preViewTop + preViewHeight) { 411 | preViewHeight = views.clientHeight - delta - preViewTop; 412 | preViewWidth = Math.floor(preViewHeight / ratio); 413 | } 414 | 415 | // if preview is not high enough forget ratios! 416 | if (preViewWidth <= Math.floor(slideViewWidth * preViewMinimumFactor)) { 417 | slideViewWidth = (views.clientWidth - delta); 418 | slideViewHeight = Math.floor((views.clientHeight - delta - preViewGap) / (1 + preViewMinimumFactor)); 419 | 420 | preViewTop = slideViewHeight + preViewGap; 421 | 422 | preViewWidth = Math.floor(slideViewWidth * preViewMinimumFactor); 423 | preViewHeight = views.clientHeight - delta - preViewTop; 424 | } 425 | 426 | // set the calculated into styles 427 | slideView.style.width = slideViewWidth + "px"; 428 | slideView.style.height = slideViewHeight + "px"; 429 | 430 | preView.style.top = preViewTop + "px"; 431 | 432 | preView.style.width = preViewWidth + "px"; 433 | preView.style.height = preViewHeight + "px"; 434 | } 435 | 436 | var init = function(css, cssPreview) { 437 | if (css !== undefined) { 438 | cssFile = css; 439 | } 440 | 441 | if (cssPreview !== undefined) { 442 | cssFileIframe = cssPreview; 443 | } 444 | 445 | // Register the event 446 | root.addEventListener('impress:stepleave', onStepLeave); 447 | root.addEventListener('impress:stepenter', onStepEnter); 448 | 449 | // When the window closes, clean up after ourselves. 450 | window.onunload = function(){ 451 | if (consoleWindow && !consoleWindow.closed) { 452 | consoleWindow.close(); 453 | } 454 | }; 455 | 456 | // Open speaker console when they press 'p' 457 | registerKeyEvent([80], open, window); 458 | // Goto a slide number when they press 'g' 459 | registerKeyEvent([71], gotoSlide, window); 460 | 461 | }; 462 | 463 | // Return the object 464 | allConsoles[rootId] = {init: init, open: open, clockTick: clockTick, registerKeyEvent: registerKeyEvent}; 465 | return allConsoles[rootId]; 466 | 467 | }; 468 | 469 | })(document, window); 470 | -------------------------------------------------------------------------------- /docs/qgis-test-project.qgs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | degrees 21 | 22 | -169.10573771796194364 23 | -89.26862795567424769 24 | 208.89426228203805636 25 | 92.98480704432577681 26 | 27 | 0 28 | 0 29 | 30 | 31 | +proj=longlat +datum=WGS84 +no_defs 32 | 3452 33 | 4326 34 | EPSG:4326 35 | WGS 84 36 | longlat 37 | WGS84 38 | true 39 | 40 | 41 | 0 42 | 43 | 44 | 45 | 46 | world20170729110147157 47 | bluemarble20170729110757938 48 | world20170729111159705 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | -180 72 | -89.90000000000000568 73 | 180 74 | 83.67470000000000141 75 | 76 | bluemarble20170729110757938 77 | contextualWMSLegend=0&crs=EPSG:4326&dpiMode=7&featureCount=10&format=image/jpeg&layers=bluemarble&styles=&url=http://localhost:8080/cgi-bin/qgis_mapserv.fcgi?MAP%3D/qgis-server/projects/helloworld.qgs 78 | 79 | 80 | 81 | bluemarble 82 | 83 | 84 | +proj=longlat +datum=WGS84 +no_defs 85 | 3452 86 | 4326 87 | EPSG:4326 88 | WGS 84 89 | longlat 90 | WGS84 91 | true 92 | 93 | 94 | 95 | 96 | 97 | wms 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 0 113 | 114 | 115 | 116 | -180 117 | -89.90000000000000568 118 | 180 119 | 83.67470000000000141 120 | 121 | world20170729110147157 122 | contextualWMSLegend=0&crs=EPSG:4326&dpiMode=7&featureCount=10&format=image/jpeg&layers=world&styles=&url=http://localhost:8081/cgi-bin/qgis_mapserv.fcgi?MAP%3D/qgis-server/projects/helloworld.qgs 123 | 124 | 125 | 126 | world 127 | 128 | 129 | +proj=longlat +datum=WGS84 +no_defs 130 | 3452 131 | 4326 132 | EPSG:4326 133 | WGS 84 134 | longlat 135 | WGS84 136 | true 137 | 138 | 139 | 140 | 141 | 142 | 143 | wms 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 0 159 | 160 | 161 | world20170729111159705 162 | user='username' password='password' restrictToRequestBBOX='1' srsname='EPSG:4326' typename='world' url='http://localhost:8081/cgi-bin/qgis_mapserv.fcgi?MAP=/qgis-server/projects/helloworld.qgs' version='auto' table="" sql= 163 | 164 | 165 | 166 | world 167 | 168 | 169 | +proj=longlat +datum=WGS84 +no_defs 170 | 3452 171 | 4326 172 | EPSG:4326 173 | WGS 84 174 | longlat 175 | WGS84 176 | true 177 | 178 | 179 | WFS 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 0 227 | 0 228 | 0 229 | NAME 230 | 231 | 232 | 251 | . 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | . 266 | 267 | 0 268 | . 269 | 270 | 0 271 | generatedlayout 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | meters 290 | m2 291 | 292 | 293 | +proj=longlat +datum=WGS84 +no_defs 294 | EPSG:4326 295 | 3452 296 | 297 | 298 | false 299 | 300 | 301 | 0 302 | 255 303 | 255 304 | 255 305 | 255 306 | 255 307 | 255 308 | 309 | 310 | 2 311 | current_layer 312 | off 313 | 0 314 | 315 | 316 | 2 317 | true 318 | 319 | 320 | false 321 | 322 | 323 | 324 | 325 | -------------------------------------------------------------------------------- /docs/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from docutils import nodes 4 | from docutils.parsers.rst import Directive, directives 5 | import sys, os 6 | import re 7 | import hovercraft 8 | from utils.graphviz_directive import Graphviz 9 | 10 | directives.register_directive('graph', Graphviz) 11 | 12 | # Need to make sure all graph images files are existing 13 | try: 14 | presentation = sys.argv[1:][0] 15 | except IndexError: 16 | presentation = 'index.rst' 17 | 18 | # Relpath 19 | rel_path = os.path.abspath(os.path.dirname(presentation)) 20 | # Make dir 21 | graphs = [] 22 | with open(presentation, 'r') as f: 23 | for l in f.readlines(): 24 | try: 25 | img = re.findall(r'.. graph::\s?(.*)$', l)[0] 26 | img_path = os.path.join(rel_path, img) 27 | img_dir = os.path.dirname(img_path) 28 | if not os.path.exists(img_dir): 29 | os.mkdir(img_dir) 30 | if not os.path.exists(img_path): 31 | with open(img_path, 'w+') as f: 32 | pass 33 | except IndexError: 34 | pass 35 | 36 | 37 | if __name__ == "__main__": 38 | cmd = [presentation] 39 | if len(sys.argv) > 2: 40 | cmd.extend(sys.argv[2:]) 41 | hovercraft.main(cmd) -------------------------------------------------------------------------------- /docs/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/utils/__init__.py -------------------------------------------------------------------------------- /docs/utils/graphviz_directive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Allow graphviz-formatted graphs to be included in rst 4 | documents inline. 5 | 6 | :copyright: Copyright 2010 by the ItOpen 7 | :copyright: Copyright 2007-2010 by the Sphinx team 8 | :license: BSD, see LICENSE for details. 9 | """ 10 | 11 | 12 | import sys 13 | from docutils import nodes, utils 14 | from docutils.parsers.rst import directives, states 15 | from docutils.parsers.rst.directives.images import Image 16 | 17 | import errno 18 | 19 | from os import path, makedirs 20 | from subprocess import Popen, PIPE 21 | 22 | 23 | # Errnos that we need. 24 | EEXIST = getattr(errno, 'EEXIST', 0) 25 | ENOENT = getattr(errno, 'ENOENT', 0) 26 | EPIPE = getattr(errno, 'EPIPE', 0) 27 | 28 | 29 | def ensuredir(path): 30 | """Ensure that a path exists.""" 31 | try: 32 | makedirs(path) 33 | except OSError as err: 34 | # 0 for Jython/Win32 35 | if err.errno not in [0, EEXIST]: 36 | raise 37 | 38 | class Graphviz(Image): 39 | """ 40 | Directive to insert arbitrary dot markup. 41 | """ 42 | has_content = True 43 | 44 | 45 | def run(self): 46 | # Raise an error if the directive does not have contents. 47 | self.assert_has_content() 48 | dotcode = '\n'.join(self.content) 49 | if not dotcode.strip(): 50 | return [self.state_machine.reporter.warning( 51 | 'Ignoring "graphviz" directive without content.', 52 | line=self.lineno)] 53 | reference = directives.uri(self.arguments[0]) 54 | self.render_dot(dotcode, reference, self.options) 55 | return super(Graphviz, self).run() 56 | 57 | 58 | def render_dot(self, code, outfn, options): 59 | """ 60 | Render graphviz code into a PNG or PDF output file. 61 | """ 62 | 63 | ensuredir(path.dirname(outfn)) 64 | 65 | format = path.splitext(outfn)[1][1:] 66 | 67 | if format not in ('png', 'svg'): 68 | raise self.error('Format must be png or svg (instead of: %s)' % format) 69 | 70 | dot_args = ['dot'] 71 | if format=='svg': 72 | dot_args.extend(['-Tsvg:cairo', '-o' + outfn]) 73 | else: 74 | dot_args.extend(['-T' + format, '-o' + outfn]) 75 | if format == 'png': 76 | dot_args.extend(['-Tcmapx', '-o%s.map' % outfn]) 77 | 78 | try: 79 | p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE) 80 | except OSError as err: 81 | if err.errno != ENOENT: # No such file or directory 82 | raise 83 | return None, None 84 | try: 85 | # Graphviz may close standard input when an error occurs, 86 | # resulting in a broken pipe on communicate() 87 | stdout, stderr = p.communicate(code.encode('utf8')) 88 | except OSError as err: 89 | if err.errno != EPIPE: 90 | raise 91 | # in this case, read the standard output and standard error streams 92 | # directly, to get the error message(s) 93 | stdout, stderr = p.stdout.read(), p.stderr.read() 94 | p.wait() 95 | if p.returncode != 0: 96 | raise self.error('dot exited with error:\n[stderr]\n%s\n' 97 | '[stdout]\n%s' % (stderr, stdout)) 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/utils/graphviz_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Test 9 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 92 | 94 | 96 | 97 | 100 | 101 | 102 |
103 |
104 |
105 | 108 | 112 |
113 |
114 |
115 |

Test

116 | 117 | images/g.png 118 |
119 |
120 | 121 | 122 | -------------------------------------------------------------------------------- /docs/utils/graphviz_test.rst: -------------------------------------------------------------------------------- 1 | .. title:: QGIS: chi paga? 2 | 3 | 4 | =========================================================== 5 | QGIS: chi paga? 6 | =========================================================== 7 | 8 | Oppure: come fare lo sviluppatore O.S. senza andare in bancarotta 9 | 10 | 11 | ---- 12 | 13 | 14 | Test 15 | ========= 16 | 17 | 18 | Uno 19 | 20 | .. graph:: images/g.png 21 | :scale: 50% 22 | 23 | digraph g { 24 | rankdir="LR" 25 | 26 | edge [fontcolor=red fontsize=9] 27 | node [shape=box style="rounded"] 28 | 29 | wmsc [label="WMS-client"] 30 | wmsc2 [label="WMS-client"] 31 | wmss [label="WMS-server" shape=box style=""] 32 | 33 | wmsc -> wmss [label="KVP request"] 34 | wmss -> wmsc2 [label="image response"] 35 | 36 | } 37 | 38 | 39 | ----- 40 | -------------------------------------------------------------------------------- /docs/utils/graphviz_test.rst.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/utils/graphviz_test.rst.html -------------------------------------------------------------------------------- /docs/utils/images/g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/docs/utils/images/g.png -------------------------------------------------------------------------------- /manual_preparation.sh: -------------------------------------------------------------------------------- 1 | sudo sh -c 'echo "qgis ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers' 2 | sudo apt install unzip 3 | wget https://github.com/elpaso/qgis3-server-vagrant/archive/master.zip 4 | unzip master.zip 5 | rm master.zip 6 | sudo mv qgis3-server-vagrant-master/ /vagrant 7 | 8 | # Become root and cd to /vagrant/provisioning 9 | # run download_only.sh 10 | -------------------------------------------------------------------------------- /provisioning/apache2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Provisioning file for Vagrant: apache2 3 | 4 | set -e 5 | 6 | . /vagrant/provisioning/config.sh 7 | 8 | echo "Changing QGIS_SERVER_DIR to ${QGIS_SERVER_DIR} ..." 9 | 10 | # Install the required server software 11 | export DEBIAN_FRONTEND=noninteractive 12 | apt-get -y install apache2 libapache2-mod-fcgid 13 | 14 | 15 | # Configure the web server 16 | cp /vagrant/resources/apache2/001-qgis-server.conf /etc/apache2/sites-available 17 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/apache2/sites-available/001-qgis-server.conf 18 | 19 | a2enmod rewrite 20 | a2enmod cgid 21 | a2dissite 000-default 22 | a2ensite 001-qgis-server 23 | 24 | # Listen on port 81 instead of 80 (nginx) 25 | sed -i -e 's/Listen 80/Listen 81/' /etc/apache2/ports.conf 26 | sed -i -e 's/VirtualHost \*:80/VirtualHost \*:81/' /etc/apache2/sites-available/001-qgis-server.conf 27 | 28 | # Restart the server 29 | service apache2 restart 30 | -------------------------------------------------------------------------------- /provisioning/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Provisioning file for Vagrant 3 | 4 | # Common setup for all servers 5 | 6 | set -e 7 | 8 | . /vagrant/provisioning/config.sh 9 | 10 | echo "Changing QGIS_SERVER_DIR to ${QGIS_SERVER_DIR} ..." 11 | 12 | # Add QGIS repositories 13 | apt-key adv --keyserver keyserver.ubuntu.com --recv-key 51F523511C7028C3 14 | echo 'deb http://qgis.org/ubuntu-nightly bionic main' > /etc/apt/sources.list.d/ubuntu-qgis.list 15 | 16 | # Update && upgrade packages 17 | apt-get update && apt-get -y upgrade 18 | 19 | # Install the software 20 | # Temporary workaround: overwrite is required because of a packaging bug 21 | apt-get -y install -o Dpkg::Options::="--force-overwrite" qgis-server python3-qgis xvfb 22 | 23 | # Install utilities (optional) 24 | apt-get -y install vim unzip ipython3 25 | 26 | # Install sample projects and plugins 27 | mkdir -p $QGIS_SERVER_DIR/logs 28 | cp -r /vagrant/resources/web/htdocs $QGIS_SERVER_DIR 29 | cp -r /vagrant/resources/web/plugins $QGIS_SERVER_DIR 30 | cp -r /vagrant/resources/web/projects $QGIS_SERVER_DIR 31 | chown -R www-data.www-data $QGIS_SERVER_DIR 32 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" $QGIS_SERVER_DIR/htdocs/index.html 33 | 34 | # Setup xvfb 35 | cp /vagrant/resources/xvfb/xvfb.service /etc/systemd/system/xvfb.service 36 | systemctl enable /etc/systemd/system/xvfb.service 37 | systemctl start xvfb 38 | 39 | # Symlink to cgi for apache CGI mode 40 | [ -f /usr/lib/cgi-bin/qgis_mapserv.cgi ] || ln -s /usr/lib/cgi-bin/qgis_mapserv.fcgi /usr/lib/cgi-bin/qgis_mapserv.cgi 41 | -------------------------------------------------------------------------------- /provisioning/config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Provisioning file for Vagrant 3 | 4 | # Common configuration 5 | export QGIS_SERVER_DIR=/qgis-server 6 | 7 | # Nuber of processes (~ number of cores) 8 | export NUM_PROCESSES=4 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | # Common variables: do not edit below this line 19 | export DEBIAN_FRONTEND=noninteractive 20 | -------------------------------------------------------------------------------- /provisioning/download_only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a partial provisioning script that download most 3 | # of the dependencies without installing, it can be useful 4 | # for workshop VM preparation to speed-up setup. 5 | 6 | 7 | set -e 8 | 9 | . /vagrant/provisioning/config.sh 10 | 11 | echo "Setting up development apt repositories ..." 12 | 13 | # Add QGIS repositories 14 | apt-key adv --keyserver keyserver.ubuntu.com --recv-key 51F523511C7028C3 15 | echo 'deb http://qgis.org/ubuntu-nightly bionic main' > /etc/apt/sources.list.d/ubuntu-qgis.list 16 | 17 | # Update && upgrade packages 18 | apt-get update && apt-get -y upgrade 19 | 20 | echo "Downloading dependencies without installing ..." 21 | 22 | # Download the software 23 | apt-get -y install --download-only \ 24 | qgis-server \ 25 | python3-qgis \ 26 | xvfb \ 27 | vim \ 28 | unzip \ 29 | ipython3 \ 30 | apache2 \ 31 | libapache2-mod-fcgid \ 32 | nginx \ 33 | uwsgi \ 34 | python3-pil \ 35 | python3-yaml \ 36 | libproj12 \ 37 | python3-shapely \ 38 | python3-pip 39 | 40 | 41 | -------------------------------------------------------------------------------- /provisioning/job.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Provisioning file for Vagrant 3 | # This script will run every time the machine starts 4 | -------------------------------------------------------------------------------- /provisioning/mapproxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Install and configure mapproxy 3 | 4 | set -e 5 | 6 | . /vagrant/provisioning/config.sh 7 | 8 | echo "Changing QGIS_SERVER_DIR to ${QGIS_SERVER_DIR} ..." 9 | 10 | # Install the software 11 | 12 | apt-get install -y python3-pil python3-yaml libproj12 python3-shapely python3-pip 13 | pip3 install virtualenv 14 | 15 | MAPPROXY_DIR=${QGIS_SERVER_DIR}/mapproxy 16 | 17 | if [ ! -e ${MAPPROXY_DIR} ]; then 18 | mkdir -p ${MAPPROXY_DIR} 19 | fi 20 | 21 | virtualenv --system-site-packages ${MAPPROXY_DIR} 22 | source ${MAPPROXY_DIR}/bin/activate 23 | pip3 install MapProxy 24 | 25 | pushd . 26 | 27 | cd ${MAPPROXY_DIR} 28 | mapproxy-util create -t base-config --force myconfig 29 | mapproxy-util create -t wsgi-app --force -f myconfig/mapproxy.yaml myconfig.py 30 | # Overwrite with prepared configuration 31 | cp /vagrant/resources/mapproxy/mapproxy.yaml ${MAPPROXY_DIR}/myconfig/ 32 | 33 | popd 34 | 35 | 36 | # Install the software 37 | apt-get -y install nginx uwsgi 38 | 39 | # Configure mapp nginx conf 40 | cp /vagrant/resources/nginx/mapproxy /etc/nginx/sites-enabled 41 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/nginx/sites-enabled/mapproxy 42 | SOCKETS='' 43 | for i in $( eval echo {1..$NUM_PROCESSES}); do SOCKETS=" server 127.0.0.1:810$i;\n$SOCKETS" ; done 44 | sed -i -e "s@QGIS_SERVER_HTTP_SERVERS@${SOCKETS}@g" /etc/nginx/sites-enabled/mapproxy 45 | cp /vagrant/resources/uwsgi/*.py ${QGIS_SERVER_DIR} 46 | chmod +x ${QGIS_SERVER_DIR}/*.py 47 | 48 | 49 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/systemd/system/mapproxy@.service 50 | 51 | echo "Changing MAPPROXY_DIR to ${MAPPROXY_DIR} ..." 52 | 53 | sed -i -e "s@MAPPROXY_DIR@${MAPPROXY_DIR}@g" /etc/systemd/system/mapproxy@.service 54 | /bin/bash -c "systemctl enable mapproxy@{1..${NUM_PROCESSES}}.service && systemctl start mapproxy@{1..${NUM_PROCESSES}}.service" 55 | 56 | 57 | chown -R www-data.www-data ${MAPPROXY_DIR} 58 | 59 | systemctl restart nginx.service 60 | -------------------------------------------------------------------------------- /provisioning/nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Provisioning file for Vagrant: nginx 3 | 4 | set -e 5 | 6 | . /vagrant/provisioning/config.sh 7 | 8 | echo "Changing QGIS_SERVER_DIR to ${QGIS_SERVER_DIR} ..." 9 | 10 | # Install the software 11 | apt-get -y install nginx uwsgi 12 | 13 | # Configure the web server 14 | if [ -e /etc/nginx/sites-enabled/default ]; then 15 | rm /etc/nginx/sites-enabled/default 16 | fi 17 | 18 | cp /vagrant/resources/nginx/qgis-* /etc/nginx/sites-enabled 19 | 20 | # Configure FastCGI nginx conf 21 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/nginx/sites-enabled/qgis-server-fcgi 22 | SOCKETS='' 23 | for i in $( eval echo {1..$NUM_PROCESSES}); do SOCKETS=" server unix:/run/qgis_mapserv$i.sock;\n$SOCKETS" ; done 24 | sed -i -e "s@QGIS_SERVER_SOCKETS@${SOCKETS}@g" /etc/nginx/sites-enabled/qgis-server-fcgi 25 | 26 | 27 | # Configure python nginx conf 28 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/nginx/sites-enabled/qgis-server-python 29 | SOCKETS='' 30 | for i in $( eval echo {1..$NUM_PROCESSES}); do SOCKETS=" server 127.0.0.1:809$i;\n$SOCKETS" ; done 31 | sed -i -e "s@QGIS_SERVER_HTTP_SERVERS@${SOCKETS}@g" /etc/nginx/sites-enabled/qgis-server-python 32 | cp /vagrant/resources/uwsgi/*.py ${QGIS_SERVER_DIR} 33 | chmod +x ${QGIS_SERVER_DIR}/*.py 34 | 35 | 36 | # Configure systemd 37 | cp /vagrant/resources/systemd/*.* /etc/systemd/system/ 38 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/systemd/system/qgis-server-fcgi@.service 39 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/systemd/system/qgis-server-fcgi@.socket 40 | /bin/bash -c "systemctl enable qgis-server-fcgi@{1..${NUM_PROCESSES}}.socket && systemctl start qgis-server-fcgi@{1..${NUM_PROCESSES}}.socket" 41 | /bin/bash -c "systemctl enable qgis-server-fcgi@{1..${NUM_PROCESSES}}.service && systemctl start qgis-server-fcgi@{1..${NUM_PROCESSES}}.service" 42 | # No socket for python 43 | sed -i -e "s@QGIS_SERVER_DIR@${QGIS_SERVER_DIR}@g" /etc/systemd/system/qgis-server-python@.service 44 | /bin/bash -c "systemctl enable qgis-server-python@{1..${NUM_PROCESSES}}.service && systemctl start qgis-server-python@{1..${NUM_PROCESSES}}.service" 45 | 46 | systemctl restart nginx.service 47 | -------------------------------------------------------------------------------- /provisioning/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Provisioning file for Vagrant 3 | # Install the software 4 | 5 | set -e 6 | 7 | # Load configuration 8 | . /vagrant/provisioning/config.sh 9 | 10 | 11 | export LC_ALL=C 12 | export DEBIAN_FRONTEND=noninteractive 13 | 14 | # Fix DNS for gnupg import key 15 | sed -i -e 's/nameserver.*/nameserver 8.8.8.8/' /etc/resolv.conf 16 | 17 | # Call provisioning scripts 18 | echo "Runnig provisioning scripts ..." 19 | /vagrant/provisioning/common.sh 20 | /vagrant/provisioning/apache2.sh 21 | /vagrant/provisioning/nginx.sh 22 | /vagrant/provisioning/mapproxy.sh 23 | 24 | # Clean 25 | echo "Cleaning up ..." 26 | apt-get autoremove -y 27 | apt-get clean 28 | 29 | echo "All done!" -------------------------------------------------------------------------------- /resources/apache2/001-qgis-server.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerAdmin webmaster@localhost 3 | DocumentRoot QGIS_SERVER_DIR/htdocs 4 | ErrorLog ${APACHE_LOG_DIR}/001-qgis-server-error.log 5 | CustomLog ${APACHE_LOG_DIR}/001-qgis-server-access.log combined 6 | 7 | # Longer timeout for WPS... default = 40 8 | FcgidIOTimeout 120 9 | FcgidInitialEnv LC_ALL "en_US.UTF-8" 10 | FcgidInitialEnv LANG "en_US.UTF-8" 11 | FcgidInitialEnv PYTHONIOENCODING UTF-8 12 | FcgidInitialEnv QGIS_DEBUG 1 13 | # Deprecated: better log to sterr (goes to /var/log/apache2/error.log) 14 | #FcgidInitialEnv QGIS_SERVER_LOG_FILE "QGIS_SERVER_DIR/logs/qgis-server-fcgi-apache.log" 15 | FcgidInitialEnv QGIS_SERVER_LOG_STDERR 1 16 | FcgidInitialEnv QGIS_SERVER_LOG_LEVEL 0 17 | FcgidInitialEnv QGIS_PLUGINPATH "QGIS_SERVER_DIR/plugins" 18 | FcgidInitialEnv QGIS_AUTH_DB_DIR_PATH "QGIS_SERVER_DIR" 19 | FcgidInitialEnv QGIS_OPTIONS_PATH "QGIS_SERVER_DIR" 20 | FcgidInitialEnv QGIS_CUSTOM_CONFIG_PATH "QGIS_SERVER_DIR" 21 | # Temporary workaround for #18230 22 | FcgidInitialEnv QGIS_PREFIX_PATH "/usr" 23 | FcgidInitialEnv DISPLAY ":99" 24 | 25 | # For simple CGI: ignored by fcgid 26 | SetEnv LC_ALL "en_US.UTF-8" 27 | SetEnv LANG "en_US.UTF-8" 28 | SetEnv PYTHONIOENCODING UTF-8 29 | SetEnv QGIS_DEBUG 1 30 | SetEnv QGIS_SERVER_LOG_FILE "QGIS_SERVER_DIR/logs/qgis-server-cgi-apache.log" 31 | # Temporary workaround for #18230 32 | SetEnv QGIS_PREFIX_PATH "/usr" 33 | SetEnv QGIS_SERVER_LOG_LEVEL 0 34 | SetEnv QGIS_PLUGINPATH "QGIS_SERVER_DIR/plugins" 35 | SetEnv QGIS_AUTH_DB_DIR_PATH "QGIS_SERVER_DIR" 36 | SetEnv QGIS_OPTIONS_PATH "QGIS_SERVER_DIR" 37 | SetEnv QGIS_CUSTOM_CONFIG_PATH "QGIS_SERVER_DIR" 38 | SetEnv DISPLAY ":99" 39 | 40 | 41 | Options FollowSymLinks 42 | AllowOverride None 43 | Order Deny,Allow 44 | Deny from All 45 | 46 | 47 | 48 | Options Indexes FollowSymLinks MultiViews 49 | AllowOverride All 50 | Allow from all 51 | Order allow,deny 52 | Require all granted 53 | 54 | 55 | # Needed for QGIS HelloServer plugin HTTP BASIC auth 56 | 57 | RewriteEngine on 58 | RewriteCond %{HTTP:Authorization} . 59 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 60 | 61 | 62 | ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ 63 | 64 | AllowOverride All 65 | Options +ExecCGI -MultiViews +FollowSymLinks 66 | Allow from all 67 | AddHandler cgi-script .cgi 68 | AddHandler fcgid-script .fcgi 69 | Require all granted 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /resources/mapproxy/mapproxy.yaml: -------------------------------------------------------------------------------- 1 | # ------------------------------- 2 | # MapProxy example configuration. 3 | # ------------------------------- 4 | # 5 | # This is a minimal MapProxy configuration. 6 | # See full_example.yaml and the documentation for more options. 7 | # 8 | 9 | # Starts the following services: 10 | # Demo: 11 | # http://localhost:8080/demo 12 | # WMS: 13 | # capabilities: http://localhost:8080/service?REQUEST=GetCapabilities 14 | # WMTS: 15 | # capabilities: http://localhost:8080/wmts/1.0.0/WMTSCapabilities.xml 16 | # first tile: http://localhost:8080/wmts/osm/webmercator/0/0/0.png 17 | # Tile service (compatible with OSM/etc.) 18 | # first tile: http://localhost:8080/tiles/osm/webmercator/0/0/0.png 19 | # TMS: 20 | # note: TMS is not compatible with OSM/Google Maps/etc. 21 | # fist tile: http://localhost:8080/tms/1.0.0/osm/webmercator/0/0/0.png 22 | # KML: 23 | # initial doc: http://localhost:8080/kml/osm/webmercator 24 | 25 | services: 26 | demo: 27 | tms: 28 | use_grid_names: true 29 | # origin for /tiles service 30 | origin: 'nw' 31 | kml: 32 | use_grid_names: true 33 | wmts: 34 | wms: 35 | md: 36 | title: MapProxy WMS Proxy 37 | abstract: This is a minimal MapProxy example. 38 | 39 | layers: 40 | - name: osm 41 | title: Omniscale OSM WMS - osm.omniscale.net 42 | sources: [osm_cache] 43 | 44 | - name: world 45 | title: QGIS Server World Demo 46 | sources: [world_cache] 47 | 48 | - name: bluemarble 49 | title: QGIS Server Bluemarble Demo 50 | sources: [bluemarble_cache] 51 | 52 | 53 | caches: 54 | osm_cache: 55 | grids: [webmercator] 56 | sources: [osm_wms] 57 | 58 | world_cache: 59 | grids: [webmercator] 60 | sources: [world_wms] 61 | 62 | bluemarble_cache: 63 | grids: [webmercator] 64 | sources: [bluemarble_wms] 65 | 66 | sources: 67 | osm_wms: 68 | type: wms 69 | req: 70 | # use of this source is only permitted for testing 71 | url: http://osm.omniscale.net/proxy/service? 72 | layers: osm 73 | 74 | world_wms: 75 | type: wms 76 | req: 77 | url: http://127.0.0.1:8091/?NOAUTH=1&MAP=/qgis-server/projects/helloworld.qgs&SERVICE=WMS&REQUEST=GetCapabilities 78 | layers: world 79 | 80 | bluemarble_wms: 81 | type: wms 82 | req: 83 | url: http://127.0.0.1:8091/?NOAUTH=1&MAP=/qgis-server/projects/helloworld.qgs&SERVICE=WMS&REQUEST=GetCapabilities 84 | layers: bluemarble 85 | 86 | grids: 87 | webmercator: 88 | base: GLOBAL_WEBMERCATOR 89 | 90 | globals: 91 | -------------------------------------------------------------------------------- /resources/nginx/mapproxy: -------------------------------------------------------------------------------- 1 | 2 | # Default mapproxy server configuration template 3 | # This is parsed by provisioning scripts to 4 | # create the final configuration file 5 | 6 | # Extract server name and port from HTTP_HOST, this 7 | # is needed because we are behind a VMs mapped port 8 | 9 | map $http_host $parsed_server_name { 10 | default $host; 11 | "~(?P[^:]+):(?P

.*+)" $h; 12 | } 13 | 14 | map $http_host $parsed_server_port { 15 | default $server_port; 16 | "~(?P[^:]+):(?P

.*+)" $p; 17 | } 18 | 19 | upstream mapproxy_backend { 20 | QGIS_SERVER_HTTP_SERVERS 21 | } 22 | 23 | server { 24 | listen 83 default_server; 25 | listen [::]:83 default_server; 26 | 27 | # This is vital 28 | underscores_in_headers on; 29 | 30 | root QGIS_SERVER_DIR/htdocs; 31 | 32 | location / { 33 | 34 | # $http_host contains the original server name and port, such as: "localhost:8080" 35 | # QGIS Server behind a VM needs this parsed values in order to automatically 36 | # get the correct values for the online resource URIs 37 | # Pass headers to proxy 38 | proxy_set_header X_FORWARDED_HOST $parsed_server_name:$parsed_server_port; 39 | 40 | proxy_pass http://mapproxy_backend; 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /resources/nginx/qgis-server-fcgi: -------------------------------------------------------------------------------- 1 | # Default QGIS server configuration template 2 | # This is parsed by provisioning scripts to 3 | # create the final configuration file 4 | 5 | # Extract server name and port from HTTP_HOST, this 6 | # is needed because we are behind a VMs mapped port 7 | 8 | map $http_host $parsed_server_name { 9 | default $host; 10 | "~(?P[^:]+):(?P

.*+)" $h; 11 | } 12 | 13 | map $http_host $parsed_server_port { 14 | default $server_port; 15 | "~(?P[^:]+):(?P

.*+)" $p; 16 | } 17 | 18 | upstream qgis_mapserv_backend { 19 | QGIS_SERVER_SOCKETS 20 | } 21 | 22 | server { 23 | listen 80 default_server; 24 | listen [::]:80 default_server; 25 | 26 | # This is vital 27 | underscores_in_headers on; 28 | 29 | root QGIS_SERVER_DIR/htdocs; 30 | 31 | location / { 32 | # First attempt to serve request as file, then 33 | # as directory, then fall back to displaying a 404. 34 | try_files $uri $uri/ =404; 35 | } 36 | 37 | # project file set by env var 38 | # example: http://localhost:8082/project_base_name 39 | location ~ ^/project/([^/]+)/?(.*)$ 40 | { 41 | set $qgis_project /qgis-server/projects/$1.qgs; 42 | rewrite ^/project/(.*)$ /cgi-bin/qgis_mapserv.fcgi last; 43 | } 44 | 45 | location /cgi-bin/ { 46 | # Disable gzip (it makes scripts feel slower since they have to complete 47 | # before getting gzipped) 48 | gzip off; 49 | 50 | # Fastcgi socket 51 | fastcgi_pass qgis_mapserv_backend; 52 | 53 | # $http_host contains the original server name and port, such as: "localhost:8080" 54 | # QGIS Server behind a VM needs this parsed values in order to automatically 55 | # get the correct values for the online resource URIs 56 | fastcgi_param SERVER_NAME $parsed_server_name; 57 | fastcgi_param SERVER_PORT $parsed_server_port; 58 | 59 | # Set project file from env var 60 | fastcgi_param QGIS_PROJECT_FILE $qgis_project; 61 | 62 | # Fastcgi parameters, include the standard ones 63 | # Note: this needs to be last or it will take precedence over the SERVER_NAME/PORT overridden above 64 | # See: https://www.digitalocean.com/community/tutorials/understanding-and-implementing-fastcgi-proxying-in-nginx 65 | include /etc/nginx/fastcgi_params; 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /resources/nginx/qgis-server-python: -------------------------------------------------------------------------------- 1 | # Default QGIS server configuration template for Python server 2 | # This is parsed by provisioning scripts to 3 | # create the final configuration file 4 | 5 | # Extract server name and port from HTTP_HOST, this 6 | # is needed because we are behind a VMs mapped port 7 | 8 | map $http_host $parsed_server_name { 9 | default $host; 10 | "~(?P[^:]+):(?P

.*+)" $h; 11 | } 12 | 13 | map $http_host $parsed_server_port { 14 | default $host; 15 | "~(?P[^:]+):(?P

.*+)" $p; 16 | } 17 | 18 | upstream qgis_mapserv_python_backend { 19 | QGIS_SERVER_HTTP_SERVERS 20 | } 21 | 22 | server { 23 | listen 82 default_server; 24 | listen [::]:82 default_server; 25 | 26 | # This is vital 27 | underscores_in_headers on; 28 | 29 | root QGIS_SERVER_DIR/htdocs; 30 | 31 | location / { 32 | # First attempt to serve request as file, then 33 | # as directory, then fall back to displaying a 404. 34 | try_files $uri $uri/ =404; 35 | } 36 | 37 | 38 | 39 | location /cgi-bin/ { 40 | 41 | # $http_host contains the original server name and port, such as: "localhost:8080" 42 | # QGIS Server behind a VM needs this parsed values in order to automatically 43 | # get the correct values for the online resource URIs 44 | # Pass headers to proxy 45 | proxy_set_header HOST $parsed_server_name; 46 | proxy_set_header PORT $parsed_server_port; 47 | proxy_set_header SERVER_NAME $parsed_server_name; 48 | proxy_set_header SERVER_PORT $parsed_server_port; 49 | proxy_pass http://qgis_mapserv_python_backend; 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /resources/qgis/wms-wfs-test-project.qgd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/qgis/wms-wfs-test-project.qgd -------------------------------------------------------------------------------- /resources/systemd/mapproxy@.service: -------------------------------------------------------------------------------- 1 | 2 | # Listen on ports 810%i 3 | # Path: /etc/systemd/system/mapproxy@.service 4 | # systemctl start mapproxy@{1..4}.service 5 | 6 | 7 | [Unit] 8 | Description = Mapproxy backend (instance %i) 9 | Wants = network.target 10 | After = network.target 11 | 12 | [Service] 13 | Restart = always 14 | User = www-data 15 | Group = www-data 16 | WorkingDirectory = MAPPROXY_DIR 17 | ExecStart = MAPPROXY_DIR/bin/mapproxy-util serve-develop -b 0.0.0.0:810%i MAPPROXY_DIR/myconfig/mapproxy.yaml 18 | 19 | [Install] 20 | WantedBy = multi-user.target -------------------------------------------------------------------------------- /resources/systemd/qgis-server-fcgi@.service: -------------------------------------------------------------------------------- 1 | 2 | # Path: /etc/systemd/system/qgis-server-fcgi@.service 3 | # systemctl start qgis-fcgi@{1..4}.service 4 | # Used by Nginx 5 | 6 | [Unit] 7 | Description = QGIS Server Tracker FastCGI backend (instance %i) 8 | 9 | [Service] 10 | User = www-data 11 | Group = www-data 12 | ExecStart = /usr/lib/cgi-bin/qgis_mapserv.fcgi 13 | StandardInput = socket 14 | #StandardOutput = null 15 | #StandardError = null 16 | StandardOutput=syslog 17 | StandardError=syslog 18 | SyslogIdentifier=qgis-server-fcgi 19 | WorkingDirectory=/tmp 20 | 21 | Restart = always 22 | 23 | # Environment 24 | Environment="QGIS_AUTH_DB_DIR_PATH=QGIS_SERVER_DIR/projects" 25 | Environment="QGIS_SERVER_LOG_FILE=QGIS_SERVER_DIR/logs/qgis-server-fcgi-nginx.log" 26 | # For stderr logging (goes to syslog) 27 | #Environment="QGIS_SERVER_LOG_FILE=''" 28 | #Environment="QGIS_SERVER_LOG_STDERR=1" 29 | Environment="QGIS_SERVER_LOG_LEVEL=0" 30 | Environment="QGIS_DEBUG=1" 31 | Environment="DISPLAY=:99" 32 | Environment="QGIS_PLUGINPATH=QGIS_SERVER_DIR/plugins" 33 | Environment="QGIS_OPTIONS_PATH=QGIS_SERVER_DIR" 34 | Environment="QGIS_CUSTOM_CONFIG_PATH=QGIS_SERVER_DIR" 35 | 36 | [Install] 37 | WantedBy = multi-user.target -------------------------------------------------------------------------------- /resources/systemd/qgis-server-fcgi@.socket: -------------------------------------------------------------------------------- 1 | 2 | # Path: /etc/systemd/system/qgis-fcgi@.socket 3 | # systemctl enable qgis-fcgi@{1..4}.socket && systemctl start qgis-fcgi@{1..4}.socket 4 | 5 | [Unit] 6 | Description = QGIS Server FastCGI Socket (instance %i) 7 | 8 | [Socket] 9 | Accept = false 10 | SocketUser = www-data 11 | SocketGroup = www-data 12 | SocketMode = 0660 13 | ListenStream = /run/qgis_mapserv%i.sock 14 | 15 | [Install] 16 | WantedBy = sockets.target -------------------------------------------------------------------------------- /resources/systemd/qgis-server-python@.service: -------------------------------------------------------------------------------- 1 | 2 | # Listen on ports 809%i 3 | # Path: /etc/systemd/systemQGIS_SERVER_DIR-python@.service 4 | # systemctl start qgis-server-python@{1..4}.service 5 | 6 | 7 | [Unit] 8 | Description = QGIS Server Tracker Python backend (instance %i) 9 | 10 | [Service] 11 | User = www-data 12 | Group = www-data 13 | ExecStart = QGIS_SERVER_DIR/qgis_wrapped_server_wsgi.py 14 | StandardInput = null 15 | #StandardOutput = null 16 | #StandardError = null 17 | StandardOutput=syslog 18 | StandardError=syslog 19 | SyslogIdentifier=qgis-server-python 20 | WorkingDirectory=/tmp 21 | 22 | Restart = always 23 | 24 | # Environment 25 | Environment=QGIS_SERVER_PORT=809%i 26 | Environment="QGIS_AUTH_DB_DIR_PATH=QGIS_SERVER_DIR/projects" 27 | Environment="QGIS_SERVER_LOG_FILE=QGIS_SERVER_DIR/logs/qgis-server-python.log" 28 | Environment="QGIS_SERVER_LOG_LEVEL=0" 29 | Environment="QGIS_DEBUG=1" 30 | # Temporary workaround for #18230 31 | Environment="QGIS_PREFIX_PATH=/usr" 32 | Environment="DISPLAY=:99" 33 | Environment="QGIS_PLUGINPATH=QGIS_SERVER_DIR/plugins" 34 | Environment="QGIS_OPTIONS_PATH=QGIS_SERVER_DIR" 35 | Environment="QGIS_CUSTOM_CONFIG_PATH=QGIS_SERVER_DIR" 36 | 37 | [Install] 38 | WantedBy = multi-user.target 39 | -------------------------------------------------------------------------------- /resources/uwsgi/README.md: -------------------------------------------------------------------------------- 1 | Uwsgi configuration does not work and hangs forever. 2 | 3 | The python script is actually used from nging proxy started with systemd 4 | -------------------------------------------------------------------------------- /resources/uwsgi/qgis-server.ini: -------------------------------------------------------------------------------- 1 | # NOT USED: SEE README.md 2 | 3 | [uwsgi] 4 | 5 | socket = /run/qgis_mapserv_wsgi.sock 6 | 7 | wsgi-file = QGIS_SERVER_DIR/qgis_wrapped_server_wsgi.py 8 | 9 | processes = 4 10 | threads = 1 11 | 12 | uid = www-data 13 | gid = www-data 14 | 15 | chdir = QGIS_SERVER_DIR 16 | 17 | master = true 18 | chmod-socket = 777 19 | vacuum = true 20 | 21 | logto = QGIS_SERVER_DIR/logs/qgis-nginx-wsgi-000.log 22 | 23 | env = QGIS_AUTH_DB_DIR_PATH=QGIS_SERVER_DIR/projects 24 | env = QGIS_SERVER_LOG_FILE=QGIS_SERVER_DIR/logs/qgis-nginx-wsgi-000.log 25 | env = QGIS_SERVER_LOG_LEVEL=0 26 | env = QGIS_DEBUG=1 27 | # Temporary workaround for #18230 28 | env = QGIS_PREFIX_PATH=/usr 29 | env = DISPLAY=:99 30 | env = QGIS_PLUGINPATH=QGIS_SERVER_DIR/plugins 31 | env = QGIS_OPTIONS_PATH=QGIS_SERVER_DIR 32 | env = QGIS_CUSTOM_CONFIG_PATH=QGIS_SERVER_DIR 33 | -------------------------------------------------------------------------------- /resources/uwsgi/qgis_wrapped_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | QGIS Server HTTP wrapper 4 | 5 | This script launches a QGIS Server listening on port 8081 or on the port 6 | specified on the environment variable QGIS_SERVER_PORT. 7 | QGIS_SERVER_HOST (defaults to 127.0.0.1) 8 | 9 | A XYZ map service is also available for multithreading testing: 10 | 11 | ?MAP=/path/to/projects.qgs&SERVICE=XYZ&X=1&Y=0&Z=1&LAYERS=world 12 | 13 | Note that multi threading in QGIS server is not officially supported and 14 | it is not supposed to work in any case 15 | 16 | Set MULTITHREADING environment variable to 1 to activate. 17 | 18 | 19 | For testing purposes, HTTP Basic can be enabled by setting the following 20 | environment variables: 21 | 22 | * QGIS_SERVER_HTTP_BASIC_AUTH (default not set, set to anything to enable) 23 | * QGIS_SERVER_USERNAME (default ="username") 24 | * QGIS_SERVER_PASSWORD (default ="password") 25 | 26 | PKI authentication with HTTPS can be enabled with: 27 | 28 | * QGIS_SERVER_PKI_CERTIFICATE (server certificate) 29 | * QGIS_SERVER_PKI_KEY (server private key) 30 | * QGIS_SERVER_PKI_AUTHORITY (root CA) 31 | * QGIS_SERVER_PKI_USERNAME (valid username) 32 | 33 | Sample run: 34 | 35 | QGIS_SERVER_PKI_USERNAME=Gerardus QGIS_SERVER_PORT=47547 QGIS_SERVER_HOST=localhost \ 36 | QGIS_SERVER_PKI_KEY=/home/$USER/dev/QGIS/tests/testdata/auth_system/certs_keys/localhost_ssl_key.pem \ 37 | QGIS_SERVER_PKI_CERTIFICATE=/home/$USER/dev/QGIS/tests/testdata/auth_system/certs_keys/localhost_ssl_cert.pem \ 38 | QGIS_SERVER_PKI_AUTHORITY=/home/$USER/dev/QGIS/tests/testdata/auth_system/certs_keys/chains_subissuer-issuer-root_issuer2-root2.pem \ 39 | python3 /home/$USER/dev/QGIS/tests/src/python/qgis_wrapped_server.py 40 | 41 | .. note:: This program is free software; you can redistribute it and/or modify 42 | it under the terms of the GNU General Public License as published by 43 | the Free Software Foundation; either version 2 of the License, or 44 | (at your option) any later version. 45 | """ 46 | 47 | __author__ = 'Alessandro Pasotti' 48 | __date__ = '05/15/2016' 49 | __copyright__ = 'Copyright 2016, The QGIS Project' 50 | # This will get replaced with a git SHA1 when you do a git archive 51 | __revision__ = '$Format:%H$' 52 | 53 | 54 | import os 55 | 56 | # Needed on Qt 5 so that the serialization of XML is consistent among all executions 57 | os.environ['QT_HASH_SEED'] = '1' 58 | 59 | import sys 60 | import signal 61 | import ssl 62 | import math 63 | import urllib.parse 64 | import importlib 65 | from http.server import BaseHTTPRequestHandler, HTTPServer 66 | from socketserver import ThreadingMixIn 67 | import threading 68 | 69 | from qgis.core import QgsApplication, QgsCoordinateTransform, QgsCoordinateReferenceSystem, QgsMessageLog 70 | from qgis.server import QgsServer, QgsServerRequest, QgsBufferServerRequest, QgsBufferServerResponse, QgsServerFilter 71 | 72 | QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8081')) 73 | QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1') 74 | # PKI authentication 75 | QGIS_SERVER_PKI_CERTIFICATE = os.environ.get('QGIS_SERVER_PKI_CERTIFICATE') 76 | QGIS_SERVER_PKI_KEY = os.environ.get('QGIS_SERVER_PKI_KEY') 77 | QGIS_SERVER_PKI_AUTHORITY = os.environ.get('QGIS_SERVER_PKI_AUTHORITY') 78 | QGIS_SERVER_PKI_USERNAME = os.environ.get('QGIS_SERVER_PKI_USERNAME') 79 | 80 | # Check if PKI - https is enabled 81 | https = (QGIS_SERVER_PKI_CERTIFICATE is not None and 82 | os.path.isfile(QGIS_SERVER_PKI_CERTIFICATE) and 83 | QGIS_SERVER_PKI_KEY is not None and 84 | os.path.isfile(QGIS_SERVER_PKI_KEY) and 85 | QGIS_SERVER_PKI_AUTHORITY is not None and 86 | os.path.isfile(QGIS_SERVER_PKI_AUTHORITY) and 87 | QGIS_SERVER_PKI_USERNAME) 88 | 89 | 90 | qgs_app = QgsApplication([], False) 91 | qgs_server = QgsServer() 92 | 93 | 94 | 95 | # Load plugins 96 | if os.environ.get('QGIS_PLUGINPATH', False): 97 | plugindir = os.environ.get('QGIS_PLUGINPATH') 98 | sys.path.append(plugindir) 99 | # Magic mistery: without this, virtual QgsServerFilter instances are not wrapped 100 | iface = qgs_server.serverInterface() 101 | try: 102 | for modulename in [(a, b, c) for a, b, c in os.walk(plugindir)][0][1]: 103 | try: 104 | module = importlib.import_module(modulename) 105 | getattr(module, 'serverClassFactory')(iface) 106 | QgsMessageLog.logMessage('Python plugin %s loaded!' % modulename) 107 | except: 108 | QgsMessageLog.logMessage('Could not load Python plugin %s!' % modulename) 109 | except: 110 | QgsMessageLog.logMessage('No plugins found in %s!' % plugindir) 111 | 112 | 113 | 114 | class Handler(BaseHTTPRequestHandler): 115 | 116 | def do_GET(self, post_body=None): 117 | # CGI vars: 118 | headers = {} 119 | for k, v in self.headers.items(): 120 | headers['%s%s' % ('HTTP_' if not k.startswith('HTTP') else '', k.replace(' ', '-').replace('-', '_').replace(' ', '-').upper())] = v 121 | if not self.path.startswith('http'): 122 | self.path = "%s://%s:%s%s" % ('https' if https else 'http', QGIS_SERVER_HOST, self.server.server_port, self.path) 123 | request = QgsBufferServerRequest(self.path, (QgsServerRequest.PostMethod if post_body is not None else QgsServerRequest.GetMethod), headers, post_body) 124 | response = QgsBufferServerResponse() 125 | qgs_server.handleRequest(request, response) 126 | 127 | headers_dict = response.headers() 128 | try: 129 | self.send_response(int(headers_dict['Status'].split(' ')[0])) 130 | except: 131 | self.send_response(200) 132 | for k, v in headers_dict.items(): 133 | self.send_header(k, v) 134 | self.end_headers() 135 | self.wfile.write(response.body()) 136 | return 137 | 138 | def do_POST(self): 139 | content_len = int(self.headers.get('content-length', 0)) 140 | post_body = self.rfile.read(content_len) 141 | return self.do_GET(post_body) 142 | 143 | 144 | class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 145 | """Handle requests in a separate thread.""" 146 | pass 147 | 148 | 149 | if __name__ == '__main__': 150 | if os.environ.get('MULTITHREADING') == '1': 151 | server = ThreadedHTTPServer((QGIS_SERVER_HOST, QGIS_SERVER_PORT), Handler) 152 | else: 153 | server = HTTPServer((QGIS_SERVER_HOST, QGIS_SERVER_PORT), Handler) 154 | if https: 155 | server.socket = ssl.wrap_socket(server.socket, 156 | certfile=QGIS_SERVER_PKI_CERTIFICATE, 157 | keyfile=QGIS_SERVER_PKI_KEY, 158 | ca_certs=QGIS_SERVER_PKI_AUTHORITY, 159 | cert_reqs=ssl.CERT_REQUIRED, 160 | server_side=True, 161 | ssl_version=ssl.PROTOCOL_TLSv1) 162 | print('Starting server on %s://%s:%s, use to stop' % 163 | ('https' if https else 'http', QGIS_SERVER_HOST, server.server_port), flush=True) 164 | 165 | def signal_handler(signal, frame): 166 | global qgs_app 167 | print("\nExiting QGIS...") 168 | qgs_app.exitQgis() 169 | sys.exit(0) 170 | 171 | signal.signal(signal.SIGINT, signal_handler) 172 | server.serve_forever() 173 | -------------------------------------------------------------------------------- /resources/uwsgi/qgis_wrapped_server_wsgi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | QGIS Server HTTP Wsgi wrapper 5 | 6 | This script launches a QGIS Server listening on port 8081 or on the port 7 | specified on the environment variable QGIS_SERVER_PORT. 8 | QGIS_SERVER_HOST (defaults to 127.0.0.1) 9 | 10 | .. note:: This program is free software; you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation; either version 2 of the License, or 13 | (at your option) any later version. 14 | """ 15 | 16 | __author__ = 'Alessandro Pasotti' 17 | __date__ = '05/15/2016' 18 | __copyright__ = 'Copyright 2016, The QGIS Project' 19 | # This will get replaced with a git SHA1 when you do a git archive 20 | __revision__ = '$Format:%H$' 21 | 22 | 23 | import os 24 | 25 | from wsgiref.simple_server import make_server 26 | from cgi import parse_qs, escape 27 | 28 | # Needed on Qt 5 so that the serialization of XML is consistent among all executions 29 | os.environ['QT_HASH_SEED'] = '1' 30 | 31 | import importlib 32 | import sys 33 | import signal 34 | import ssl 35 | import math 36 | import urllib.parse 37 | from http.server import BaseHTTPRequestHandler, HTTPServer 38 | from socketserver import ThreadingMixIn 39 | import threading 40 | 41 | from qgis.core import QgsApplication, QgsCoordinateTransform, QgsCoordinateReferenceSystem, QgsMessageLog 42 | from qgis.server import QgsServer, QgsServerRequest, QgsBufferServerRequest, QgsBufferServerResponse, QgsServerFilter 43 | 44 | QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8081')) 45 | QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1') 46 | 47 | 48 | qgs_app = QgsApplication([], False) 49 | qgs_server = QgsServer() 50 | 51 | 52 | # Load plugins 53 | if os.environ.get('QGIS_PLUGINPATH', False): 54 | plugindir = os.environ.get('QGIS_PLUGINPATH') 55 | sys.path.append(plugindir) 56 | # Magic mistery: without this, virtual QgsServerFilter instances are not wrapped 57 | iface = qgs_server.serverInterface() 58 | try: 59 | for modulename in [(a, b, c) for a, b, c in os.walk(plugindir)][0][1]: 60 | try: 61 | module = importlib.import_module(modulename) 62 | getattr(module, 'serverClassFactory')(iface) 63 | QgsMessageLog.logMessage('Python plugin %s loaded!' % modulename) 64 | except: 65 | QgsMessageLog.logMessage('Could not load Python plugin %s!' % modulename) 66 | except: 67 | QgsMessageLog.logMessage('No plugins found in %s!' % plugindir) 68 | 69 | 70 | 71 | class application(object): 72 | """QGIS Server WSGI application""" 73 | 74 | def __init__(self, environ, start_fn): 75 | self.environ = environ 76 | self.start_fn = start_fn 77 | 78 | def __iter__(self): 79 | if self.environ['REQUEST_METHOD'] == 'POST': 80 | yield self.do_POST() 81 | else: 82 | yield self.do_GET() 83 | 84 | def do_GET(self, post_body=None): 85 | # CGI vars: 86 | headers = {} 87 | for k, v in self.environ.copy().items(): 88 | headers['%s%s' % ('HTTP_' if not k.startswith('HTTP_') else '', k.replace(' ', '-').replace('-', '_').replace(' ', '-').upper())] = str(v) 89 | path = self.environ.get('PATH_INFO', '/') 90 | if not path.startswith('http'): 91 | path = "%s://%s:%s%s?%s" % (self.environ.get('wsgi.url_scheme'), headers.get('HTTP_HOST'), headers.get('HTTP_PORT'), path, self.environ.get('QUERY_STRING')) 92 | request = QgsBufferServerRequest(path, (QgsServerRequest.PostMethod if post_body is not None else QgsServerRequest.GetMethod), headers, post_body) 93 | response = QgsBufferServerResponse() 94 | qgs_server.handleRequest(request, response) 95 | 96 | 97 | headers_dict = response.headers() 98 | response_headers = [(k, v) for k, v in headers_dict.items()] 99 | try: 100 | self.start_fn(headers_dict['Status'], response_headers) 101 | except: 102 | self.start_fn('200 OK',response_headers) 103 | return bytes(response.body()) 104 | 105 | def do_POST(self): 106 | content_len = int(self.headers.get('content-length', 0)) 107 | post_env = self.environ.copy() 108 | post_env['QUERY_STRING'] = '' 109 | post_body = cgi.FieldStorage( 110 | fp= self.environ['wsgi.input'], 111 | environ=post_env, 112 | keep_blank_values=True 113 | ) 114 | return self.do_GET(post_body) 115 | 116 | 117 | if __name__ == '__main__': 118 | from wsgiref.simple_server import make_server 119 | server = make_server(QGIS_SERVER_HOST, QGIS_SERVER_PORT, application) 120 | print('Starting server on %s://%s:%s, use to stop' % 121 | ('http', QGIS_SERVER_HOST, QGIS_SERVER_PORT), flush=True) 122 | 123 | def signal_handler(signal, frame): 124 | global qgs_app 125 | print("\nExiting QGIS...") 126 | qgs_app.exitQgis() 127 | sys.exit(0) 128 | 129 | server.serve_forever() 130 | -------------------------------------------------------------------------------- /resources/uwsgi/uwsgi-qgis.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Starts QGIS Server as FastCGI uwsgi app 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/uwsgi --ini /etc/uwsgi/apps-enabled/qgis-server.ini 7 | User=www-data 8 | Group=www-data 9 | Restart=on-failure 10 | KillSignal=SIGQUIT 11 | Type=notify 12 | StandardError=syslog 13 | NotifyAccess=all 14 | 15 | [Install] 16 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /resources/web/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | HeaderColumnWidths=1835,147,198 3 | Timestamp=2016,4,11,14,43,20 4 | Version=3 5 | ViewMode=1 6 | VisibleRoles=CustomizedDetails,Details_text,Details_size,Details_date,Details_permissions,Details_owner,Details_group 7 | -------------------------------------------------------------------------------- /resources/web/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 |

QGIS Server Demo

14 |

HTTP Basic authentication: qgis/qgis

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
ServerPortMapped to
Nginx FastCGI808080
Apache (Fast)CGI818081
Nginx Python828082
Nginx mapproxy838083
47 |

Place your HTML content here!

48 |

or browse one of the default endpoints:

49 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/README.rst: -------------------------------------------------------------------------------- 1 | Simple Browser Python plugin for QGIS Server 2 | ============================================ 3 | 4 | This plugin adds a stylesheet to `GetProjectsettings` `WMS` requests, generating 5 | an HTML page with the project's layer tree and a link to a OpenLayers map 6 | preview of the layers. 7 | 8 | .. image:: assets/toctree.png 9 | 10 | 11 | The layer preview is generated by adding an new `application/openlayers` `FORMAT` to `GetMap` 12 | requests. 13 | 14 | .. image:: assets/preview.png 15 | 16 | 17 | For more informations on how to use Python plugins for QGIS Server, please refer 18 | to a series of posts on our company website: 19 | 20 | http://www.itopen.it/category/gis/qgis/qgis-server/ 21 | 22 | -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/ServerSimpleBrowser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | *************************************************************************** 5 | ServerSimpleBrowseer.py 6 | --------------------- 7 | Date : August 2014 8 | Copyright : (C) 2014-2015 by Alessandro Pasotti 9 | Email : apasotti at gmail dot com 10 | *************************************************************************** 11 | * * 12 | * This program is free software; you can redistribute it and/or modify * 13 | * it under the terms of the GNU General Public License as published by * 14 | * the Free Software Foundation; either version 2 of the License, or * 15 | * (at your option) any later version. * 16 | * * 17 | *************************************************************************** 18 | """ 19 | 20 | __author__ = 'Alessandro Pasotti' 21 | __date__ = 'March 2016' 22 | __copyright__ = '(C) 2016, Alessandro Pasotti - ItOpen' 23 | 24 | import sys 25 | import os 26 | import codecs 27 | import re 28 | 29 | # Import the PyQt and QGIS libraries 30 | from qgis.PyQt.QtCore import * 31 | from qgis.PyQt.QtGui import * 32 | from qgis.core import * 33 | from qgis.server import * 34 | 35 | 36 | class ServerSimpleFilter(QgsServerFilter): 37 | 38 | def get_url(self, request, params): 39 | url = 'http://' if not self.serverInterface().getEnv("HTTPS") == 'on' else 'https://' 40 | 41 | # First check for standard header 42 | http_host = self.serverInterface().requestHandler().requestHeader("Host") 43 | if http_host: 44 | url += http_host.strip() 45 | else: 46 | server_name = self.serverInterface().getEnv("SERVER_NAME") 47 | if not server_name: 48 | server_name = self.serverInterface().requestHandler().requestHeader("HTTP_HOST") 49 | server_port = self.serverInterface().getEnv("SERVER_PORT") 50 | if not server_port: 51 | server_port = self.serverInterface().requestHandler().requestHeader("HTTP_PORT") 52 | url += server_name 53 | url += ':' + server_port 54 | 55 | script_name = self.serverInterface().getEnv("SCRIPT_NAME") 56 | if not script_name: 57 | script_name = self.serverInterface().requestHandler().requestHeader("HTTP_PATH_INFO") 58 | 59 | url += script_name 60 | url += '?MAP=' + params.get('MAP', '') 61 | return url 62 | 63 | 64 | def requestReady(self): 65 | request = self.serverInterface().requestHandler() 66 | params = request.parameterMap( ) 67 | if params.get('SERVICE', '').lower() == 'wms' \ 68 | and params.get('REQUEST', '').lower() == 'getmap' \ 69 | and params.get('FORMAT', '').lower() == 'application/openlayers': 70 | QgsMessageLog.logMessage("OpenLayersFilter.requestReady FORMAT application/openlayers") 71 | request.setParameter('SERVICE', 'OPENLAYERS') 72 | 73 | 74 | def responseComplete(self): 75 | 76 | # TODO: error checking 77 | request = self.serverInterface().requestHandler() 78 | params = request.parameterMap( ) 79 | 80 | 81 | if params.get('SERVICE', '').lower() == 'openlayers': 82 | request.clear() 83 | request.setResponseHeader('Content-type', 'text/html; charset=utf-8') 84 | minx, miny, maxx, maxy = params.get('BBOX', '0,0,0,0').split(',') 85 | url = self.get_url(request, params) 86 | QgsMessageLog.logMessage("OpenLayersFilter.responseComplete URL %s" % url, 'plugin') 87 | f = open(os.path.join(os.path.dirname(__file__), 'assets' , 'map_template.html')) 88 | body = ''.join(f.readlines()) 89 | f.close() 90 | body = body % { 91 | 'extent' : params.get('BBOX', ''), 92 | 'url' : url, 93 | 'layers': params.get('LAYERS', ''), 94 | 'center': center, 95 | 'zoom': params.get('ZOOM', '12'), 96 | 'height': params.get('HEIGHT', 400), 97 | 'width': params.get('WIDTH', 600), 98 | 'projection' : params.get('CRS', 'EPSG:4326'), 99 | } 100 | request.setStatusCode(200) 101 | request.appendBody(body.encode('utf8')) 102 | QgsMessageLog.logMessage("OpenLayersFilter.responseComplete BODY %s" % body, 'plugin') 103 | return 104 | 105 | 106 | if params.get('SERVICE', '').lower() == 'wms' \ 107 | and params.get('REQUEST', '').lower() == 'xsl': 108 | request.clear() 109 | request.setResponseHeader('Content-type', 'text/xml; charset=utf-8') 110 | f = open(os.path.dirname(__file__) + '/assets/' + 'getprojectsettings.xsl') 111 | body = ''.join(f.readlines()) 112 | f.close() 113 | request.setStatusCode(200) 114 | request.appendBody(body.encode('utf8')) 115 | return 116 | 117 | 118 | if params.get('SERVICE', '').lower() == 'wms' \ 119 | and params.get('REQUEST', '').lower() == 'getprojectsettings': 120 | # inject XSL code 121 | body = request.body() 122 | request.clearBody() 123 | url = self.get_url(request, params).encode('utf8') 124 | body = body.replace(b'', b'\n' % url) 125 | request.appendBody(body) 126 | return 127 | 128 | 129 | class ServerSimpleBrowser: 130 | """Plugin for QGIS server""" 131 | 132 | def __init__(self, serverIface): 133 | # Save reference to the QGIS server interface 134 | self.serverIface = serverIface 135 | try: 136 | self.serverIface.registerFilter(ServerSimpleFilter(serverIface), 1000) 137 | except Exception as e: 138 | QgsLogger.debug("ServerSimpleBrowser- Error loading filter %s", e) 139 | 140 | 141 | 142 | class SimpleBrowser: 143 | def __init__(self, iface): 144 | # Save reference to the QGIS interface 145 | self.iface = iface 146 | self.canvas = iface.mapCanvas() 147 | 148 | 149 | def initGui(self): 150 | # Create action that will start plugin 151 | self.action = QAction(QIcon(":/plugins/"), "About Server SimpleBrowser", self.iface.mainWindow()) 152 | # Add toolbar button and menu item 153 | self.iface.addPluginToMenu("Server SimpleBrowser", self.action) 154 | # connect the action to the run method 155 | QObject.connect(self.action, SIGNAL("activated()"), self.about) 156 | 157 | def unload(self): 158 | # Remove the plugin menu item and icon 159 | self.iface.removePluginMenu("Server SimpleBrowser", self.action) 160 | 161 | # run 162 | def about(self): 163 | QMessageBox.information(self.iface.mainWindow(), QCoreApplication.translate('SimpleBrowser', "Server SimpleBrowser"), QCoreApplication.translate('SimpleBrowser', "Server SimpleBrowser is a simple browser plugin for QGIS Server, it does just nothing in QGIS Desktop. See: plugin's homepage")) 164 | 165 | 166 | 167 | if __name__ == "__main__": 168 | pass 169 | -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from .ServerSimpleBrowser import ServerSimpleBrowser 9 | return ServerSimpleBrowser(serverIface) 10 | 11 | def classFactory(iface): 12 | from .ServerSimpleBrowser import SimpleBrowser 13 | return SimpleBrowser(iface) 14 | 15 | -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/assets/getprojectsettings.xsl: -------------------------------------------------------------------------------- 1 | 2 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | QGIS Server Explorer 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 65 | 71 | 72 | 73 |
74 |
75 |
76 |

77 | 78 | QGIS Server Explorer

79 |

80 |

81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
NameTitle 
97 |
98 |
99 |
100 |
101 | © 2016 - Alessandro Pasotti - ItOpen 102 |
103 |
104 |
105 |
106 | 107 | 108 | 109 | 110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | View 127 | 128 | 129 | 130 | 131 | 132 | 133 |
134 | -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/assets/map_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | QGIS Server Explorer 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | QGIS Map Explorer 24 | 75 | 76 | 77 |
78 |
79 |
80 |

%(layers)s

81 |
82 | 86 |
87 |
88 |
89 |
90 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/assets/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/plugins/ServerSimpleBrowser/assets/preview.png -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/assets/toctree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/plugins/ServerSimpleBrowser/assets/toctree.png -------------------------------------------------------------------------------- /resources/web/plugins/ServerSimpleBrowser/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS Server Browser 3 | qgisMinimumVersion=2.5 4 | qgisMaximumVersion=2.99 5 | description=Simple QGIS Server WMS Browser 6 | version=version 1.2 7 | author=Alessandro Pasotti (ItOpen) 8 | email=elpaso@itopen.it 9 | ; if True it's a server plugin 10 | server=True 11 | 12 | changelog= 13 | 1.3: support projections 14 | 1.2: fixes XSLT onload event not firing in FF 15 | 1.1: fixes Desktop iface (added a fake one) 16 | 17 | external_deps=none,really 18 | 19 | tags=server, openlayers, browser 20 | 21 | tracker=https://github.com/elpaso/qgis-server-simple-browser/issues 22 | homepage=http://www.itopen.it/qgis-server-simple-browser-plugin/ 23 | repository=https://github.com/elpaso/qgis-server-browser-format 24 | 25 | category=server 26 | 27 | experimental=True 28 | 29 | about=Adds a stylesheet to GetProjectSettings requests and creates a simple project browser with OpenLayers map preview. 30 | 31 | -------------------------------------------------------------------------------- /resources/web/plugins/accesscontrol/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . accesscontrol import AccessControl 9 | return AccessControl(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/accesscontrol/accesscontrol.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qgis.server import * 4 | from qgis.core import QgsMessageLog 5 | import base64 6 | 7 | 8 | class RestrictedAccessControl(QgsAccessControlFilter): 9 | 10 | """ Used to have restriction access """ 11 | 12 | # Be able to deactivate the access control to have a reference point 13 | _active = True 14 | 15 | def __init__(self, server_iface): 16 | super(QgsAccessControlFilter, self).__init__(server_iface) 17 | 18 | def layerFilterExpression(self, layer): 19 | """ Return an additional expression filter """ 20 | 21 | QgsMessageLog.logMessage("Accesscontrol name %s" % layer.name()) 22 | QgsMessageLog.logMessage("Accesscontrol shortName %s" % layer.shortName()) 23 | 24 | if not self._active: 25 | return super(RestrictedAccessControl, self).layerFilterExpression(layer) 26 | 27 | return "\"ISO2\" = 'IT'" if layer.shortName() == "restricted" else None 28 | 29 | def layerFilterSubsetString(self, layer): 30 | """ Return an additional subset string (typically SQL) filter """ 31 | 32 | if not self._active: 33 | return super(RestrictedAccessControl, self).layerFilterSubsetString(layer) 34 | 35 | if layer.shortName() == "restricted": 36 | return "\"ISO2\" = 'IT'" 37 | else: 38 | return None 39 | 40 | def layerPermissions(self, layer): 41 | """ Return the layer rights """ 42 | 43 | if not self._active: 44 | return super(RestrictedAccessControl, self).layerPermissions(layer) 45 | 46 | rh = self.serverInterface().requestHandler() 47 | rights = QgsAccessControlFilter.LayerPermissions() 48 | # Used to test WFS transactions 49 | if rh.parameter("LAYER_PERM") == "no" and rh.parameterMap()["LAYER_PERM"] == "no": 50 | return rights 51 | # Used to test the WCS 52 | if rh.parameter("TEST") == "dem" and rh.parameterMap()["TEST"] == "dem": 53 | rights.canRead = layer.shortName() != "dem" 54 | else: 55 | rights.canRead = layer.shortName() != "Country" 56 | if layer.name() == "db_point": 57 | rights.canRead = rights.canInsert = rights.canUpdate = rights.canDelete = True 58 | 59 | return rights 60 | 61 | def authorizedLayerAttributes(self, layer, attributes): 62 | """ Return the authorised layer attributes """ 63 | 64 | if not self._active: 65 | return super(RestrictedAccessControl, self).authorizedLayerAttributes(layer, attributes) 66 | 67 | if "ISO1" in attributes: # spellok 68 | attributes.remove("ISO1") # spellok 69 | return attributes 70 | 71 | def allowToEdit(self, layer, feature): 72 | """ Are we authorise to modify the following geometry """ 73 | 74 | if not self._active: 75 | return super(RestrictedAccessControl, self).allowToEdit(layer, feature) 76 | 77 | return feature.attribute("ISO2") in ["IT", "CH"] 78 | 79 | def cacheKey(self): 80 | """Cache key to used to create the capabilities cache, "" for no cache""" 81 | 82 | return "r" if self._active else "f" 83 | 84 | 85 | 86 | 87 | class AccessControl: 88 | 89 | def __init__(self, serverIface): 90 | # Save reference to the QGIS server interface 91 | serverIface.registerAccessControl( RestrictedAccessControl(serverIface), 100 ) 92 | 93 | -------------------------------------------------------------------------------- /resources/web/plugins/accesscontrol/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS AccessControl Plugin 3 | qgisMinimumVersion=3.0 4 | description=AccessControl Plugin 5 | version=version 1.0 6 | author=Alessandro Pasotti (ItOpen) 7 | email=elpaso@itopen.it 8 | ; if True it's a server plugin 9 | server=True 10 | 11 | changelog= 12 | 13 | about=AccessControl test -------------------------------------------------------------------------------- /resources/web/plugins/customapi/__init__.py: -------------------------------------------------------------------------------- 1 | # Custom API plugin example 2 | 3 | def serverClassFactory(serverIface): 4 | from . customapi import CustomApi 5 | return CustomApi(serverIface) 6 | 7 | -------------------------------------------------------------------------------- /resources/web/plugins/customapi/circle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | Circle 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 | 49 |
50 | 51 | 52 |
53 | 54 | 55 | 56 | 57 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /resources/web/plugins/customapi/customapi.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import os 4 | 5 | from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream, QRegularExpression 6 | from qgis.server import ( 7 | QgsServiceRegistry, 8 | QgsService, 9 | QgsServerFilter, 10 | QgsServerOgcApi, 11 | QgsServerQueryStringParameter, 12 | QgsServerOgcApiHandler, 13 | ) 14 | 15 | from qgis.core import ( 16 | QgsMessageLog, 17 | QgsJsonExporter, 18 | QgsCircle, 19 | QgsFeature, 20 | QgsPoint, 21 | QgsGeometry, 22 | ) 23 | 24 | 25 | class CustomApiHandler(QgsServerOgcApiHandler): 26 | 27 | def __init__(self): 28 | super(CustomApiHandler, self).__init__() 29 | self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON]) 30 | 31 | def path(self): 32 | return QRegularExpression("/customapi") 33 | 34 | def operationId(self): 35 | return "CustomApiXYCircle" 36 | 37 | def summary(self): 38 | return "Creates a circle around a point" 39 | 40 | def description(self): 41 | return "Creates a circle around a point" 42 | 43 | def linkTitle(self): 44 | return "Custom Api XY Circle" 45 | 46 | def linkType(self): 47 | return QgsServerOgcApi.data 48 | 49 | def handleRequest(self, context): 50 | """Simple Circle""" 51 | 52 | values = self.values(context) 53 | x = values['x'] 54 | y = values['y'] 55 | r = values['r'] 56 | f = QgsFeature() 57 | f.setAttributes([x, y, r]) 58 | f.setGeometry(QgsCircle(QgsPoint(x, y), r).toCircularString()) 59 | exporter = QgsJsonExporter() 60 | self.write(json.loads(exporter.exportFeature(f)), context) 61 | 62 | def templatePath(self, context): 63 | return os.path.join(os.path.dirname(__file__), 'circle.html') 64 | 65 | def parameters(self, context): 66 | return [QgsServerQueryStringParameter('x', True, QgsServerQueryStringParameter.Type.Double, 'X coordinate'), 67 | QgsServerQueryStringParameter( 68 | 'y', True, QgsServerQueryStringParameter.Type.Double, 'Y coordinate'), 69 | QgsServerQueryStringParameter('r', True, QgsServerQueryStringParameter.Type.Double, 'radius')] 70 | 71 | 72 | class CustomApi(): 73 | 74 | def __init__(self, serverIface): 75 | api = QgsServerOgcApi(serverIface, '/customapi', 76 | 'custom api', 'a custom api', '1.1') 77 | handler = CustomApiHandler() 78 | api.registerHandler(handler) 79 | serverIface.serviceRegistry().registerApi(api) 80 | -------------------------------------------------------------------------------- /resources/web/plugins/customapi/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS Server Custom API Plugin 3 | qgisMinimumVersion=3.9 4 | description=QGIS Server Custom API Plugin 5 | version=version 1.0 6 | author=Alessandro Pasotti (ItOpen) 7 | email=elpaso@itopen.it 8 | ; if True it's a server plugin 9 | server=True 10 | 11 | changelog= 12 | 13 | about=Adds Custom API 14 | -------------------------------------------------------------------------------- /resources/web/plugins/customservice/__init__.py: -------------------------------------------------------------------------------- 1 | # Custom service plugin example 2 | 3 | def serverClassFactory(serverIface): 4 | from . customservice import CustomService 5 | return CustomService(serverIface) 6 | 7 | -------------------------------------------------------------------------------- /resources/web/plugins/customservice/customservice.py: -------------------------------------------------------------------------------- 1 | 2 | # coding=utf-8 3 | """" Simple SERVICE custom implementation 4 | 5 | http://localhost:8080/cgi-bin/qgis_mapserv.cgi?MAP=/qgis-server/projects/helloworld.qgs&SERVICE=CUSTOM 6 | 7 | .. note:: This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | """ 13 | 14 | __author__ = 'elpaso@itopen.it' 15 | __date__ = '2019-08-26' 16 | __copyright__ = 'Copyright 2019, ItOpen' 17 | 18 | 19 | from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream 20 | from qgis.server import (QgsServiceRegistry, 21 | QgsService, QgsServerFilter) 22 | from qgis.core import QgsMessageLog 23 | 24 | class CustomServiceService(QgsService): 25 | 26 | def __init__(self): 27 | QgsService.__init__(self) 28 | 29 | def name(self): 30 | return "CUSTOM" 31 | 32 | def version(self): 33 | return "1.0.0" 34 | 35 | def allowMethod(method): 36 | return True 37 | 38 | def executeRequest(self, request, response, project): 39 | response.setStatusCode(200) 40 | QgsMessageLog.logMessage('Custom service executeRequest') 41 | response.write("Custom service executeRequest") 42 | 43 | 44 | class CustomService(): 45 | 46 | def __init__(self, serverIface): 47 | self.serv = CustomServiceService() 48 | serverIface.serviceRegistry().registerService(CustomServiceService()) 49 | 50 | -------------------------------------------------------------------------------- /resources/web/plugins/customservice/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS Server Custom Service Plugin 3 | qgisMinimumVersion=3.0 4 | description=QGIS Server Custom Service Plugin 5 | version=version 1.0 6 | author=Alessandro Pasotti (ItOpen) 7 | email=elpaso@itopen.it 8 | ; if True it's a server plugin 9 | server=True 10 | 11 | changelog= 12 | 13 | about=Adds Custom Service 14 | -------------------------------------------------------------------------------- /resources/web/plugins/debug/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . debug import Debug 9 | return Debug(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/debug/debug.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """" Simple DEBUG SERVICE plugin 3 | 4 | http://localhost:8080/cgi-bin/qgis_mapserv.cgi?SERVICE=DEBUG 5 | 6 | .. note:: This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | """ 12 | 13 | __author__ = 'elpaso@itopen.it' 14 | __date__ = '2019-08-26' 15 | __copyright__ = 'Copyright 2019, ItOpen' 16 | 17 | 18 | 19 | 20 | import os 21 | 22 | from qgis.server import * 23 | from qgis.core import QgsMessageLog 24 | 25 | 26 | class DebugFilter(QgsServerFilter): 27 | 28 | def requestReady(self): 29 | handler = self.serverInterface().requestHandler() 30 | params = handler.parameterMap( ) 31 | if 'DEBUG' in params: 32 | handler.setParameter('SERVICE', 'DEBUG') 33 | 34 | def responseComplete(self): 35 | handler = self.serverInterface().requestHandler() 36 | params = handler.parameterMap( ) 37 | if params.get('SERVICE') != 'DEBUG': 38 | return 39 | 40 | headers_dict = handler.requestHeaders() 41 | handler.clear() 42 | handler.setResponseHeader('Status', '200 Ok') 43 | handler.appendBody(b'

Params

') 44 | for k in params: 45 | handler.appendBody(('

%s "%s"

' % (k, params.get(k))).encode('utf8')) 46 | 47 | handler.appendBody('

Headers

'.encode('utf8')) 48 | for k in headers_dict: 49 | handler.appendBody(('

%s "%s"

' % (k, headers_dict.get(k))).encode('utf8')) 50 | 51 | 52 | class Debug: 53 | 54 | def __init__(self, serverIface): 55 | # Save reference to the QGIS server interface 56 | # Higher priority 57 | serverIface.registerFilter( DebugFilter(serverIface), 10 ) 58 | 59 | -------------------------------------------------------------------------------- /resources/web/plugins/debug/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS Server Debug Plugin 3 | qgisMinimumVersion=3.0 4 | description=QGIS Server Debug Plugin 5 | version=version 1.0 6 | author=Alessandro Pasotti (ItOpen) 7 | email=elpaso@itopen.it 8 | ; if True it's a server plugin 9 | server=True 10 | 11 | changelog= 12 | 13 | about=Print request and environment 14 | 15 | -------------------------------------------------------------------------------- /resources/web/plugins/dummycache/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . dummycache import DummyCache 9 | return DummyCache(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/dummycache/dummycache.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """"description 3 | 4 | .. note:: This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | """ 10 | 11 | __author__ = 'elpaso@itopen.it' 12 | __date__ = '2019-02-28' 13 | __copyright__ = 'Copyright 2019, ItOpen' 14 | 15 | 16 | from qgis.server import QgsServerCacheFilter 17 | from qgis.core import QgsMessageLog 18 | 19 | from qgis.PyQt.QtCore import QByteArray 20 | import hashlib 21 | 22 | 23 | class StupidCache(QgsServerCacheFilter): 24 | """A simple in-memory and not-shared cache for demonstration purposes""" 25 | 26 | _cache = {} 27 | 28 | def _get_hash(self, request): 29 | paramMap = request.parameters() 30 | urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) 31 | m = hashlib.md5() 32 | m.update(urlParam.encode('utf8')) 33 | return m.hexdigest() 34 | 35 | def getCachedDocument(self, project, request, key): 36 | QgsMessageLog.logMessage('getCachedDocument has %s cached documents' % len(self._cache)) 37 | QgsMessageLog.logMessage('getCachedDocument keys: %s' % self._cache.keys()) 38 | if 'CLEAR_CACHE' in request.parameters().keys(): 39 | QgsMessageLog.logMessage('Cache cleared') 40 | self._cache = {} 41 | hash = self._get_hash(request) 42 | try: 43 | result = self._cache[self._get_hash(request)] 44 | QgsMessageLog.logMessage('Returning cached document %s' % hash) 45 | return result.toByteArray() 46 | except KeyError: 47 | QgsMessageLog.logMessage('No cached document %s' % hash) 48 | return QByteArray() 49 | 50 | def setCachedDocument(self, doc, project, request, key): 51 | hash = self._get_hash(request) 52 | QgsMessageLog.logMessage('Cached document added %s' % hash) 53 | self._cache[hash] = doc 54 | return True 55 | 56 | 57 | class DummyCache: 58 | """Cache plugin: this gets loaded by the server at start and 59 | creates the cache filter. 60 | """ 61 | 62 | def __init__(self, serverIface): 63 | """Register the cache""" 64 | 65 | serverIface.registerServerCache(StupidCache(serverIface), 100 ) 66 | 67 | -------------------------------------------------------------------------------- /resources/web/plugins/dummycache/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS Server Dummy Cache Plugin 3 | qgisMinimumVersion=3.0 4 | description=QGIS Server Dummy Cache Plugin 5 | version=version 1.0 6 | author=Alessandro Pasotti (ItOpen) 7 | email=elpaso@itopen.it 8 | ; if True it's a server plugin 9 | server=True 10 | 11 | changelog= 12 | 13 | about=A dummy cache for testing the cache manager and filters 14 | 15 | -------------------------------------------------------------------------------- /resources/web/plugins/getfeatureinfo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . getfeatureinfo import GetFeatureInfo 9 | return GetFeatureInfo(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/getfeatureinfo/getfeatureinfo.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """"Simple plugin example for GetFeatureInfo CSS injection 3 | 4 | http://localhost:8080/cgi-bin/qgis_mapserv.fcgi?MAP=/qgis-server/projects/helloworld.qgs&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=world&LAYERS=world&INFO_FORMAT=text%2Fhtml&I=50&J=50&CRS=EPSG%3A4326&STYLES=&WIDTH=101&HEIGHT=101&BBOX=-16.101558208465576%2C-28.4765625%2C54.914066791534424%2C42.5390625 5 | 6 | .. note:: This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | """ 12 | 13 | __author__ = 'elpaso@itopen.it' 14 | __date__ = '2019-08-26' 15 | __copyright__ = 'Copyright 2019, ItOpen' 16 | 17 | 18 | import os 19 | 20 | from qgis.server import * 21 | 22 | class GetFeatureInfoFilter(QgsServerFilter): 23 | 24 | def __init__(self, serverIface): 25 | super(GetFeatureInfoFilter, self).__init__(serverIface) 26 | 27 | def responseComplete(self): 28 | handler = self.serverInterface().requestHandler() 29 | params = handler.parameterMap( ) 30 | if params.get('TEST', False): 31 | handler.clearBody() 32 | handler.appendBody(b"ciao") 33 | 34 | if (params.get('SERVICE', '').upper() == 'WMS' \ 35 | and params.get('REQUEST', '').upper() == 'GETFEATUREINFO' \ 36 | and params.get('INFO_FORMAT', '').upper() == 'TEXT/HTML' \ 37 | and not handler.exceptionRaised() ): 38 | body = handler.body() 39 | body.replace(b'', b"""""") 40 | handler.clearBody() 41 | handler.appendBody(body) 42 | 43 | 44 | 45 | class GetFeatureInfo: 46 | 47 | def __init__(self, serverIface): 48 | # Save reference to the QGIS server interface 49 | serverIface.registerFilter( GetFeatureInfoFilter(serverIface), 100 ) 50 | -------------------------------------------------------------------------------- /resources/web/plugins/getfeatureinfo/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS GetFeatureInfo Plugin 3 | qgisMinimumVersion=2.8 4 | qgisMaximumVersion=2.99 5 | description=GetFeatureInfo Plugin 6 | version=version 1.0 7 | author=Alessandro Pasotti (ItOpen) 8 | email=elpaso@itopen.it 9 | ; if True it's a server plugin 10 | server=True 11 | 12 | changelog= 13 | 14 | about=Adds a GetFeatureInfo link to download a large file (QGIS sources) -------------------------------------------------------------------------------- /resources/web/plugins/httpbasic/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . httpbasic import HTTPBasic 9 | return HTTPBasic(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/httpbasic/httpbasic.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 4 | 5 | WARNING: this is only a demo and it is not for use in production: 6 | username and password are shown in the clear to the user 7 | 8 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 9 | 10 | """ 11 | 12 | import os 13 | 14 | from qgis.server import * 15 | from qgis.core import QgsMessageLog 16 | import base64 17 | 18 | USERNAME='qgis' 19 | PASSWORD='qgis' 20 | 21 | class HTTPBasicFilter(QgsServerFilter): 22 | 23 | def _getAuthHeader(self): 24 | """Check for various header incarnations of Authorization""" 25 | 26 | return self.serverInterface().getEnv('HTTP_AUTHORIZATION') or \ 27 | self.serverInterface().requestHandler().requestHeader('HTTP_AUTHORIZATION') or \ 28 | self.serverInterface().requestHandler().requestHeader('Authorization') 29 | 30 | 31 | def _checkAuth(self): 32 | # NOAUTH for wget / GET retrieving 33 | if 'NOAUTH' in self.serverInterface().requestHandler().parameterMap(): 34 | return True 35 | auth = self._getAuthHeader() 36 | if auth: 37 | username, password = base64.b64decode(auth[6:]).split(b':') 38 | if (username.decode('utf-8') == os.environ.get('QGIS_SERVER_USERNAME', USERNAME) and 39 | password.decode('utf-8') == os.environ.get('QGIS_SERVER_PASSWORD', PASSWORD)): 40 | return True 41 | return False 42 | 43 | def requestReady(self): 44 | handler = self.serverInterface().requestHandler() 45 | 46 | if self._checkAuth(): 47 | return 48 | 49 | handler.setParameter('SERVICE', 'ACCESS_DENIED') 50 | 51 | def responseComplete(self): 52 | handler = self.serverInterface().requestHandler() 53 | if self._checkAuth(): 54 | return 55 | # No auth ... 56 | handler.clear() 57 | handler.setResponseHeader('Status', '401 Authorization required') 58 | handler.setStatusCode(401) 59 | handler.setResponseHeader('WWW-Authenticate', 'Basic realm="QGIS Server (%s/%s)"' % (os.environ.get('QGIS_SERVER_USERNAME', USERNAME), os.environ.get('QGIS_SERVER_PASSWORD', PASSWORD))) 60 | handler.appendBody(b'

Authorization required

') 61 | 62 | 63 | 64 | class HTTPBasic: 65 | 66 | def __init__(self, serverIface): 67 | # Save reference to the QGIS server interface 68 | serverIface.registerFilter( HTTPBasicFilter(serverIface), 10 ) 69 | 70 | -------------------------------------------------------------------------------- /resources/web/plugins/httpbasic/metadata.txt: -------------------------------------------------------------------------------- 1 | ; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2 | ; 3 | ; WARNING: this is only a demo and it is not for use in production: 4 | ; username and password are shown in the clear to the user 5 | ; 6 | ; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 7 | 8 | 9 | [general] 10 | name=!!!! DEMO: ++ NOT FOR PRODUCTION ++ QGIS Server HTTP Basic Auth Plugin !!!! 11 | qgisMinimumVersion=2.8 12 | qgisMaximumVersion=3.99 13 | description=QGIS Server HTTP Basic Auth Plugin 14 | version=version 1.0 15 | author=Alessandro Pasotti (ItOpen) 16 | email=elpaso@itopen.it 17 | ; if True it's a server plugin 18 | server=True 19 | 20 | changelog= 21 | 22 | about=Adds HTTP Basic Auth to WFS POST requests -------------------------------------------------------------------------------- /resources/web/plugins/loadcustomexpressions/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . loadcustomexpressions import LoadCustomExpressions 9 | return LoadCustomExpressions(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/loadcustomexpressions/loadcustomexpressions.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qgis.server import * 4 | 5 | # ########## Load the custom expressions ########## 6 | from qgis.utils import qgsfunction 7 | from qgis.core import QgsExpression 8 | from random import uniform 9 | 10 | @qgsfunction(args='auto', group='QGIS Workshop') 11 | def custom_qgis_function(value1, value2, feature, parent): 12 | return round(uniform(value1, value2), 2) 13 | 14 | 15 | QgsExpression.registerFunction(custom_qgis_function) 16 | # ########## Done loading custom expressions ########## 17 | 18 | 19 | class LoadCustomExpressionsFilter(QgsServerFilter): 20 | 21 | def __init__(self, serverIface): 22 | super(LoadCustomExpressionsFilter, self).__init__(serverIface) 23 | 24 | 25 | class LoadCustomExpressions: 26 | 27 | def __init__(self, serverIface): 28 | # Save reference to the QGIS server interface 29 | serverIface.registerFilter(LoadCustomExpressionsFilter(serverIface), 100) 30 | -------------------------------------------------------------------------------- /resources/web/plugins/loadcustomexpressions/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS LoadCustomExpressions Plugin 3 | qgisMinimumVersion=2.99 4 | qgisMaximumVersion=3.99 5 | description=LoadCustomExpressions Plugin 6 | version=version 1.0 7 | author=Tudor Bărăscu (QTIBIA Engineering) 8 | email=tudor.barascu@qtibia.ro 9 | ; if True it's a server plugin 10 | server=True 11 | 12 | changelog= 13 | 14 | about=Adds custom expressions to be used on the server side 15 | -------------------------------------------------------------------------------- /resources/web/plugins/xyz/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This script initializes the plugin, making it known to QGIS. 4 | """ 5 | 6 | 7 | def serverClassFactory(serverIface): 8 | from . xyz import XYZ 9 | return XYZ(serverIface) 10 | -------------------------------------------------------------------------------- /resources/web/plugins/xyz/metadata.txt: -------------------------------------------------------------------------------- 1 | [general] 2 | name=QGIS Server XYZPlugin 3 | qgisMinimumVersion=3.0 4 | description=QGIS Server XYZPlugin 5 | version=version 1.0 6 | author=Alessandro Pasotti (ItOpen) 7 | email=elpaso@itopen.it 8 | ; if True it's a server plugin 9 | server=True 10 | 11 | changelog= 12 | 13 | about=Adds XYZ 14 | -------------------------------------------------------------------------------- /resources/web/plugins/xyz/xyz.py: -------------------------------------------------------------------------------- 1 | import os 2 | import math 3 | from qgis.server import * 4 | from qgis.core import QgsMessageLog 5 | 6 | def num2deg(xtile, ytile, zoom): 7 | """This returns the NW-corner of the square. Use the function with xtile+1 and/or ytile+1 8 | to get the other corners. With xtile+0.5 & ytile+0.5 it will return the center of the tile.""" 9 | n = 2.0 ** zoom 10 | lon_deg = xtile / n * 360.0 - 180.0 11 | lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n))) 12 | lat_deg = math.degrees(lat_rad) 13 | return (lat_deg, lon_deg) 14 | 15 | 16 | class XYZFilter(QgsServerFilter): 17 | """XYZ server, example: ?MAP=/path/to/projects.qgs&SERVICE=XYZ&X=1&Y=0&Z=1&LAYERS=world""" 18 | 19 | def safeGetInt(self, handler, param_name): 20 | try: 21 | return int(handler.parameter(param_name)) 22 | except ValueError: 23 | return 0 24 | 25 | def requestReady(self): 26 | QgsMessageLog.logMessage('XYZ requestReady called!') 27 | handler = self.serverInterface().requestHandler() 28 | if handler.parameter('SERVICE') == 'XYZ': 29 | x = self.safeGetInt(handler, 'X') 30 | y = self.safeGetInt(handler, 'Y') 31 | z = self.safeGetInt(handler, 'Z') 32 | # NW corner 33 | lat_deg, lon_deg = num2deg(x, y, z) 34 | # SE corner 35 | lat_deg2, lon_deg2 = num2deg(x + 1, y + 1, z) 36 | handler.setParameter('SERVICE', 'WMS') 37 | handler.setParameter('REQUEST', 'GetMap') 38 | handler.setParameter('VERSION', '1.3.0') 39 | handler.setParameter('SRS', 'EPSG:4326') 40 | handler.setParameter('HEIGHT', '256') 41 | handler.setParameter('WIDTH', '256') 42 | handler.setParameter('BBOX', "{},{},{},{}".format(lat_deg2, lon_deg, lat_deg, lon_deg2)) 43 | 44 | 45 | class XYZ: 46 | def __init__(self, serverIface): 47 | serverIface.registerFilter( XYZFilter(serverIface), 100 ) 48 | 49 | -------------------------------------------------------------------------------- /resources/web/projects/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | HeaderColumnWidths=1839,144,198,149 3 | Timestamp=2016,4,11,14,42,2 4 | Version=3 5 | ViewMode=1 6 | VisibleRoles=Details_text,Details_size,Details_date,Details_owner,CustomizedDetails 7 | -------------------------------------------------------------------------------- /resources/web/projects/Readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/Readme.txt -------------------------------------------------------------------------------- /resources/web/projects/bluemarble.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/bluemarble.tiff -------------------------------------------------------------------------------- /resources/web/projects/helloworld.qgd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/helloworld.qgd -------------------------------------------------------------------------------- /resources/web/projects/world.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/world.dbf -------------------------------------------------------------------------------- /resources/web/projects/world.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]] -------------------------------------------------------------------------------- /resources/web/projects/world.qix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/world.qix -------------------------------------------------------------------------------- /resources/web/projects/world.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/world.shp -------------------------------------------------------------------------------- /resources/web/projects/world.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elpaso/qgis3-server-vagrant/e0b0bdbeb91abaae401291525494dc9e6abe3674/resources/web/projects/world.shx -------------------------------------------------------------------------------- /resources/xvfb/xvfb.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=X Virtual Frame Buffer Service 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/Xvfb :99 -screen 0 1024x768x24 7 | 8 | [Install] 9 | WantedBy=multi-user.target 10 | --------------------------------------------------------------------------------