Existe-t-il un moyen simple d'utiliser Postgis GeoJSON dans Openlayers 3

8

J'utilisais google-vector-couches et leaflet-vector-couches de Jason Sanford ( https://github.com/JasonSanford ) pour facilement afficher, styliser et ajouter des popups personnalisés pour les données des bases de données Postgis. Cela fonctionne en combinaison avec une version modifiée de la base de données PHP-GeoJSON de Bryan McBride.

N'y a-t-il rien de comparable à utiliser avec Openlayers 3? Je dois admettre que je n'ai pas les compétences en programmation pour écrire une telle bibliothèque. Peut-être que quelqu'un connaît un code comparable. Après avoir fait beaucoup de recherches sur Google, j'ai trouvé beaucoup de réponses à des problèmes particuliers et j'ai réussi à implémenter des choses sur la façon de charger GeoJSON à l'aide d'Ajax et de la stratégie de boundingBox, de styliser les couches vectorielles et d'ajouter des popups, mais je manque toujours une solution facile façon sur la façon de mettre toutes ces choses ensemble.

Je me demande s'il y a peut-être une solution existante que je n'ai pas trouvée jusqu'à aujourd'hui, car je pense que PostGis> GeoJSON> Openlayers 3 (y compris le style et l'affichage des fonctionnalités via une popup) devrait être un moyen assez standardisé?

geom
la source
gis.stackexchange.com/questions/134688/… . Vous ajoutez l'URL dans le cadre du src de la couche vectorielle (que, bien sûr, vous pouvez styliser).
John Powell
J'ai déjà étudié ce post, mais comme je l'ai déjà mentionné, il n'y a pas de solution tout-en-un comme les scripts vectoriels.
geom
Vous voudrez peut-être ajouter un exemple montrant le fonctionnement des scripts vectoriels, car ce n'est pas du tout évident d'un lien vers une page github, et ce qui manque dans les exemples OL3 à cet égard.
John Powell

Réponses:

7

Comme il n'y avait pas de réponses à mes questions, j'ai essayé de m'inspirer d'autres codes déjà existants et j'ai continué à développer un constructeur GeoJSON de base dans openlayers 3, qui répond à mes besoins.

Je peux vraiment

  • charger différentes couches depuis PostGIS en passant un nom de table, des champs de table et une clause WHERE (avec toute la magie possible de PosgreSQL / PostGis)
  • les styliser (en utilisant des valeurs uniques, uniques ou de plage)
  • afficher / masquer les calques à min / max Résolution
  • l'identifiant unique doit être id pour éviter que les fonctionnalités soient chargées plusieurs fois
  • les couches sont chargées via la stratégie ol.loadingstrategy.bbox afin que seules les données visibles soient chargées
  • définir des modèles contextuels spécifiques aux calques
  • ajouter un étiquetage spécifique au calque

C'est donc déjà un package amusant, qui peut être adapté (et amélioré) très facilement. Par conséquent, je veux partager le code ici. L'ensemble se compose de trois parties:

  • ol3Vector.js une classe étendue de ol.layer.Vector
  • un fichier map.js dans lequel la carte et toutes les couches sont définies
  • fichier get_geojson.php qui est utilisé côté serveur pour créer la chaîne sql et renvoyer un GeoJSON valide.

Pour fonctionner correctement, ol3Vector.js doit d'abord être chargé. Voici le code:

 // class to load vector layers from Postgis using a modified php-script from
 // Bryan McBride
 // https://github.com/bmcbride/PHP-Database-GeoJSON 
 //
 ol3Vector = function(options) {

 //
 // -------- Defining default style settings ----------
 //
 var fill = new ol.style.Fill({
    color: 'rgba(255,255,255,0.4)'
 });
 var stroke = new ol.style.Stroke({
    color: '#3399CC',
    width: 1.25
 });
 var text = new ol.style.Text({
    text: "",
    font: "16px Calibri,sans-serif",
    fill: new ol.style.Fill({
        color: [255, 255, 255, 1]
    }),
    stroke: new ol.style.Stroke({
        color: [0, 0, 0, 1],
        width: 2.5
    })
});

var image = new ol.style.Circle({  // actually point styling only works with image, and not with icons
    fill: fill,
    stroke: stroke,
    radius: 5
});

var style = new ol.style.Style({
    image: image,
    fill: fill,
    stroke: stroke,
    text: text
});

var styles = [style];

//
// ------------- defining options to build the new ol3Vector-layer -----------
//
var options = {
    title: options.title,
    visible: false,
    geotable: options.geotable,  // table name in PostGis-database
    fields: options.fields,      // field-names
    where: options.where,        // where-string passed to PostGis
    source: new ol.source.Vector({
        projection: "EPSG:4326",
        attributions: [new ol.Attribution({
            html: options.attribution
        })],
        strategy: ol.loadingstrategy.bbox,   //load only data off the visible map
        loader: function(extent, resolution, projection) {
            var extent = ol.proj.transformExtent(extent, projection.getCode(), ol.proj.get('EPSG:4326').getCode());
            $.ajax({
                type: "GET",
                dataType: "json",
                url: "./mapdata/get_geojson.php?" +     // define path to the get_geojson.php script
                    "geotable=" + options.geotable +
                    "&fields=" + options.fields +
                    "&where=" + options.where +
                    "&bbox=" + extent.join(","),
                context: this
            }).done(function(data) {
                var format = new ol.format.GeoJSON();
                this.addFeatures(format.readFeatures(data, {
                    dataProjection: "EPSG:4326",
                    featureProjection: "EPSG:3857"
                }));

            });

        }
    }),
    minResolution: options.minResolution,
    maxResolution: options.maxResolution,
    content: options.content,
    symbology: options.symbology,
    showLabels: options.showLabels,
    label: options.label,
    style: (function label_style() { // style function needed to be wrapped in a function to get it work
        // ------ labeling -----------
        var layerLabel = "";
        if (!options.showLabels) {
            layerLabel = "";
        } else if (options.showLabels) {
            layerLabel = options.label;
        }
        return function(feature, resolution) {
            style.getText().setText(feature.get(layerLabel));

            // ------ styling -------------
            if (!options.symbology) {
                return style;
                //            return false;        
            } else if (options.symbology) {

                var atts = feature.getProperties();

                switch (options.symbology.type) {
                    case "single":
                        // Its a single symbology for all features so just set the values for fill and stroke
                        //
                        switch (feature.getGeometry().getType()) {
                            case "Point":
                            case "MultiPoint":

                                style.image = style.getImage().getFill().setColor(options.symbology.styleOptions.fill);
                                style.image = style.getImage().getStroke().setColor(options.symbology.styleOptions.color);
                                style.image = style.getImage().getStroke().setWidth(options.symbology.styleOptions.width);
                                style.image = style.getImage().setRadius(options.symbology.styleOptions.radius);
                                break;

                            case "LineString":
                            case "MultiLineString":
                            case "Polygon":
                            case "MultiPolygon":

                                style.fill = style.setFill(new ol.style.Fill({
                                    color: options.symbology.styleOptions.fill
                                }));
                                style.stroke = style.setStroke(new ol.style.Stroke({
                                    color: options.symbology.styleOptions.color,
                                    width: options.symbology.styleOptions.width
                                }));
                                break;
                        }
                        break;

                    case "unique":
                        // Its a unique symbology. Check if the features property value matches that in the symbology and style accordingly
                        // 
                        var att = options.symbology.property;
                        for (var i = 0, len = options.symbology.values.length; i < len; i++) { // field with values to define styles
                            if (atts[att] == options.symbology.values[i].value) { // unique value to identify style
                                for (var key in options.symbology.values[i].styleOptions) {

                                    switch (feature.getGeometry().getType()) {
                                        case "Point":
                                        case "MultiPoint":

                                            style.image = style.getImage().getFill().setColor(options.symbology.values[i].styleOptions.fill);
                                            style.image = style.getImage().getStroke().setColor(options.symbology.values[i].styleOptions.color);
                                            style.image = style.getImage().getStroke().setWidth(options.symbology.values[i].styleOptions.width);
                                            style.image = style.getImage().setRadius(options.symbology.values[i].styleOptions.radius);
                                            break;

                                        case "LineString":
                                        case "MultiLineString":
                                        case "Polygon":
                                        case "MultiPolygon":

                                            style.fill = style.setFill(new ol.style.Fill({
                                                color: options.symbology.values[i].styleOptions.fill
                                            }));
                                            style.stroke = style.setStroke(new ol.style.Stroke({
                                                color: options.symbology.values[i].styleOptions.color,
                                                width: options.symbology.values[i].styleOptions.width
                                            }));
                                            break;
                                    }
                                }
                            }
                        }
                        break;

                    case "range":
                        // Its a range symbology. Check if the features property value is in the range set in the symbology and style accordingly
                        //
                        var att = options.symbology.property;
                        for (var i = 0, len = options.symbology.ranges.length; i < len; i++) {
                            if (atts[att] >= options.symbology.ranges[i].range[0] && atts[att] <= options.symbology.ranges[i].range[1]) {
                                for (var key in options.symbology.ranges[i].styleOptions) {

                                    switch (feature.getGeometry().getType()) {
                                        case "Point":
                                        case "MultiPoint":

                                            style.image = style.getImage().getFill().setColor(options.symbology.ranges[i].styleOptions.fill);
                                            style.image = style.getImage().getStroke().setColor(options.symbology.ranges[i].styleOptions.color);
                                            style.image = style.getImage().getStroke().setWidth(options.symbology.ranges[i].styleOptions.width);
                                            style.image = style.getImage().setRadius(options.symbology.ranges[i].styleOptions.radius);
                                            break;

                                        case "LineString":
                                        case "MultiLineString":
                                        case "Polygon":
                                        case "MultiPolygon":

                                            style.fill = style.setFill(new ol.style.Fill({
                                                color: options.symbology.ranges[i].styleOptions.fill
                                            }));
                                            style.stroke = style.setStroke(new ol.style.Stroke({
                                                color: options.symbology.ranges[i].styleOptions.color,
                                                width: options.symbology.ranges[i].styleOptions.width
                                            }));
                                            break;
                                    }
                                }
                            }
                        }

                        break;
                }
            }
            return styles;
        }
    })()
}

ol.layer.Vector.call(this, options);

};

ol.inherits(ol3Vector, ol.layer.Vector);

ici un exemple de map.js

function init() {
document.removeEventListener('DOMContentLoaded', init);

//
// ------- Layers and Map ----------------
//
var baselayers = new ol.layer.Group({  
    title: 'Baselayers',
    layers: [
        new ol.layer.Tile({
            title: 'OSM',
            type: 'base',
            visible: true,
            source: new ol.source.OSM()
        })
    ]
});

var toplayers = new ol.layer.Group({
    title: 'Toplayer',
    layers: []
});


var view = new ol.View({
    center: ol.proj.fromLonLat([6.2, 49.6]),
    zoom: 12
});

var popup_div = document.getElementById('popup');
var popup = new ol.Overlay({
    element: popup_div,
    positioning: 'bottom-center',
    stopEvent: false
});

var map = new ol.Map({
    target: 'map',
    view: view,
    controls: ol.control.defaults().extend([
        new ol.control.Zoom(),
        new ol.control.FullScreen(),
        new ol.control.ZoomSlider(),
        new ol.control.LayerSwitcher({  //layergroups are used with the layerswitcher from https://github.com/walkermatt/ol3-layerswitcher
            tipLabel: 'Layer Switcher'
        }),
        new ol.control.OverviewMap(),
        new ol.control.ScaleLine()
    ]),
    overlays: [popup],
    layers: [baselayers, toplayers],
    interactions: ol.interaction.defaults().extend([
        new ol.interaction.Select({
            layers: [baselayers, toplayers]
        })
    ])
});

//
// ------------ define toplayers -------------------
//
var n2k_dh_l = new ol3Vector({
    title: "Natura 2000 Habitats Directive",   // name of the layer to show up in the layerswitcher
    attribution: "<br />Réseau Natura 2000 Habitats Directive",
    geotable: "n2k_dh",
    fields: "gid as id, sitecode, sitename, surfha",
    where: "sitename ilike '%moselle%'",    // You can use all the PostgreSQL or PostGis features here
    symbology: {
        type: "single",
        styleOptions: {
            fill: "rgba(100,250,0,0.1)",   // define colors as rgba()
            color: "green",                // or simple color-names
            width: 2
        }
    },
    minResolution: 0.01,
    maxResolution: 50,
    content: "<p><strong> BH {sitecode}</strong><hr>{sitename}<br />{surfha} ha </p>",
    showLabels: true,    // show labels on map
    label: "sitename"    // field used for labeling
});

var n2k_do_l = new ol3Vector({
    title: "Natura 2000 Birds Directive",
    attribution: "<br />Réseau Natura 2000 Birds Directive",
    geotable: "n2k_do",
    fields: "gid as id, sitecode, sitename, surfha",
    where: "",
    symbology: {
        type: "single",
        styleOptions: {
            fill: "rgba(100,250,0,0.1)",
            color: "magenta",
            width: 2
        }
    },
    minResolution: 0.01,
    maxResolution: 50,
    content: "<p><strong> BD {sitecode}</strong><hr>{sitename}<br />{surfha} ha </p>",
    showLabels: true,
    label: "sitecode"
});

var communes = new ol3Vector({
    map: map,
    title: "Communes",
    attribution: "<br />Communes",
    geotable: "communes",
    fields: "gid as id, surfha, commune, stats",
    where: "",
    symbology: {
        type: "unique",
        property: "stats",  // field used to style depending on their value
        values: [{
                value: "AB", 
                styleOptions: {
                    fill: "rgba(0,0,0,0.0)",
                    color: "grey",
                    width: 1.25
                }
            },
            {
                value: "CD",
                styleOptions: {
                    fill: "rgba(100,250,0,0.1)",
                    color: "green",
                    width: 2
                }
            },
            {
                value: "",
                styleOptions: {
                    fill: "rgba(250,0,0,0.1)",
                    color: "red",
                    width: 2
                }
            }
        ]
    },
    minResolution: 0.01,
    maxResolution: 50,
    content: "<p><strong>{commune}</strong><hr>{stat}<br />{surfha} ha </p>",
    showLabels: true,   
    label: "commune"    
});

var n2k_dh_r = new ol3Vector({
    map: map,
    title: "Natura 2000 Habitats Directive - site size",
    attribution: "<br />Réseau Natura 2000 Habitats Directive",
    geotable: "n2k_dh",
    fields: "gid as id, sitecode, sitename, surfha",
    where: "",
    symbology: {
        type: "range",
        property: "surfha", // field that holds values that should be displayed as ranges
        ranges: [{
            range: [1, 100], // defining range min and max values of property field
            styleOptions: {
                fill: "rgba(220,20,60,0.3)",
                color: "crimson",
                width: 1
            }
        }, {
            range: [101, 1000],
            styleOptions: {
                fill: "rgba(255,165,0,0.3)",
                color: "orange",
                width: 1
            }
        }, {
            range: [1001, 10000],
            styleOptions: {
                fill: "rgba(255,255,0,0.3)",
                color: "Yellow",
                width: 1
            }
        }]
    },
    minResolution: 0.01,
    maxResolution: 50,
    content: "<p><strong> BH {sitecode}</strong><hr>{sitename}<br />{surfha} ha </p>",
    showLabels: true,
    label: "sitecode"
});

var btk_p = new ol3Vector({
    map: map,
    title: "Habitats points",  // point layers
    attribution: "<br />Cartho",
    geotable: "btk_p",
    fields: "gid as id, btyp1_code, btyp1_name, cs",
    where: "",
    symbology: {
        type: "unique",
        property: "cs",
        values: [{
                value: "A", 
                styleOptions: {
                    fill: "rgba(0,128,0,0.3)",
                    color: "rgba(0,128,0,0.3)",
                    width: 1,
                    radius: 20 // must be set in order to render point features    
                }
            },
            {
                value: "B",
                styleOptions: {
                    fill: "rgba(255,165,0,0.3)",
                    color: "rgba(255,165,0,0.3)",
                    width: 1,
                    radius: 15
                }
            },
            {
                value: "C",
                styleOptions: {
                    fill: "rgba(255,0,0,0.3)",
                    color: "rgba(255,0,0,0.3)",
                    width: 1,
                    radius: 10
                }
            }
        ]
    },
    minResolution: 0.01,
    maxResolution: 50,
    content: "<p><strong>{btyp1_code}</strong><hr>{btyp1_name}<br />{cs}</p>",
    showLabels: false,
    label: "bewertung1"
});

// ------------------ add ol3Vectors to toplayers ----------
// 
toplayers.setLayers(new ol.Collection([n2k_do_l, n2k_dh_l, communes, n2k_dh_r, btk_p]));
//
// ------------------ show popups based on content-template for different layers --------------------
//  
map.on('click', function(evt) {
    var feature = map.forEachFeatureAtPixel(evt.pixel,
        function(feature, layer) {
            return feature;
        });
    var popupContent = map.forEachLayerAtPixel(evt.pixel,
        function(layer) {
            return layer.get('content');
        });
    if (feature) {
        popup.setPosition(evt.coordinate);
        var atts = feature.getProperties();
        for (var prop in atts) {
            var re = new RegExp("{" + prop + "}", "g");
            popupContent = popupContent.replace(re, atts[prop]);
        }
        $(popup_div).attr('data-placement', 'auto');
        $(popup_div).attr('data-content', popupContent);
        $(popup_div).attr('data-html', true);
        $(popup_div).popover();
        $(popup_div).popover('show');
        $('.popover-title').click(function() {
            $(popup_div).popover('destroy');
        });
    } else {
        $(popup_div).popover('destroy');
    }
});
} // End function init()
document.addEventListener('DOMContentLoaded', init);

Enfin, nous avons besoin de get_geojson.php pour récupérer les données de la base de données PostGis.

<?php
/**
 * GET GeoJSON from PostGIS
 * Query a PostGIS table or view and return the results in GeoJSON format, suitable for use in OpenLayers, Leaflet, etc.
 * Author:  Bryan R. McBride, GISP, adapted by G.Moes
 * Contact: bryanmcbride.com
 * GitHub:  https://github.com/bmcbride/PHP-Database-GeoJSON
 * 
 * @param       string      $geotable       The PostGIS layer name *REQUIRED*
 * @param       string      $geomfield      The PostGIS geometry field *REQUIRED*
 * @param       string      $srid           The SRID of the returned GeoJSON *OPTIONAL (If omitted, EPSG: 2169 will be used)*
 * @param       string      $fields         Fields to be returned *OPTIONAL (If omitted, all fields will be returned)* 
 *                              NOTE- Uppercase field names should be wrapped in double quotes
 * @param       string      $parameters     SQL WHERE clause parameters *OPTIONAL*
 * @param       string      $orderby        SQL ORDER BY constraint *OPTIONAL*
 * @param       string      $sort           SQL ORDER BY sort order (ASC or DESC) *OPTIONAL*
 * @param       string      $limit          Limit number of results returned *OPTIONAL*
 * @param       integer     $precision      digits of returned geojson 6 = 0.111 m submeter as DEFAULT *OPTIONAL*
 * @param       real        $simplify       simplify geometry to >5.0m as DEFAULT *OPTIONAL*  
 * @param       string      $offset         Offset used in conjunction with limit *OPTIONAL*
 * @return      string                  resulting geojson string
 */


# Connect to PostgreSQL database You need to pass here the credentials to connect to Your database
require("../database/connect.php");


function escapeJsonString($value) { # list from www.json.org: (\b backspace, \f formfeed)
  $escapers = array("\\", "/", "\"", "\n", "\r", "\t", "\x08", "\x0c");
  $replacements = array("\\\\", "\\/", "\\\"", "\\n", "\\r", "\\t", "\\f", "\\b");
  $result = str_replace($escapers, $replacements, $value);
  return $result;
}


# Retrive URL variables
if (empty($_GET['geotable'])) {
    echo "missing required parameter: <i>geotable</i>";
    exit;
} else
    $geotable = $_GET['geotable'];
if (empty($_GET['geomfield'])) {
    $geomfield='the_geom';
} else
    $geomfield = $_GET['geomfield'];
if (empty($_GET['srid'])) {
    $srid = 2169;    // changethis if You need another standard SRID
} else
    $srid = $_GET['srid'];
if (empty($_GET['fields'])) {
    $fields = '*';
} else
    $fields = $_GET['fields'];
$parameters = $_GET['where'];
$bbox = $_GET['bbox'];
if (empty($_GET['precision'])) {
    $precision = 6;    // change this to Your needs
} else
    $precision = $_GET['precision'];
if (empty($_GET['simplify'])) {
    $simplify = 5.0;   // change this to Your needs
} else
    $simplify = $_GET['simplify'];    
$orderby    = $_GET['orderby'];
if (empty($_GET['sort'])) {
    $sort = 'ASC';
} else
    $sort = $_GET['sort'];
$limit      = $_GET['limit'];
$offset     = $_GET['offset'];



# Build SQL SELECT statement and return the geometry as a GeoJSON element in EPSG: 4326
$sql = "SELECT " . pg_escape_string($fields) . ", st_asgeojson(st_transform(ST_SimplifyPreserveTopology(" . pg_escape_string($geomfield) . ",".$simplify."),4326),".$precision.") AS geojson FROM " . pg_escape_string($geotable);
if (strlen(trim($parameters)) > 0) {

      $sql .= " WHERE " . str_replace("''", "'", pg_escape_string($parameters));
}
if (strlen(trim($parameters)) > 0 AND strlen(trim($bbox)) > 0){
     $sql .= " AND the_geom &&  st_transform(st_makeenvelope(".pg_escape_string ($bbox).",4326),".$srid.")";
}
if (strlen(trim($parameters)) <= 0 AND strlen(trim($bbox)) > 0) {
      $sql .= " WHERE the_geom &&  st_transform(st_makeenvelope(".pg_escape_string ($bbox).",4326),".$srid.")";
}
if (strlen(trim($orderby)) > 0) {
    $sql .= " ORDER BY " . pg_escape_string($orderby) . " " . $sort;
}
if (strlen(trim($limit)) > 0) {
    $sql .= " LIMIT " . pg_escape_string($limit);
}
if (strlen(trim($offset)) > 0) {
    $sql .= " OFFSET " . pg_escape_string($offset);
}
//echo $sql;
# Try query or error
$rs = pg_query($db_handle, $sql);
if (!$rs) {
    echo "An SQL error occured.\n";
    echo $sql;
    exit;
}
# Build GeoJSON
$output    = '';
$rowOutput = '';
while ($row = pg_fetch_assoc($rs)) {
    $rowOutput = (strlen($rowOutput) > 0 ? ',' : '') . '{"type": "Feature", "geometry": ' . $row['geojson'] . ', "properties": {';
    $props = '';
    $id    = '';
    foreach ($row as $key => $val) {
        if ($key != "geojson") {
            $props .= (strlen($props) > 0 ? ',' : '') . '"' . $key . '":"' . escapeJsonString($val) . '"';
        }
        if ($key == "id") {
            $id .= ',"id":"' . escapeJsonString($val) . '"';
        }
    }

    $rowOutput .= $props . '}';
    $rowOutput .= $id;
    $rowOutput .= '}';
    $output .= $rowOutput;
}
$output = '{"type": "FeatureCollection", "features": [ ' . $output . ' ]}';

header('Content-type:application/json;charset=utf-8');
echo json_encode( $output);
?>

S'amuser! Que cela aide quelqu'un à aller plus loin ou à améliorer ce code et à le publier sur GitHub.

geom
la source
Existe-t-il une fonction POSTGIS pour améliorer considérablement les performances? Je parle de fonctionnalités 50k ish à la fois
Revo
Peut-être: new ol.source.ImageVector ()?
Revo
1
Ces scripts (JS et PHP) ne doivent pas être utilisés comme indiqué ici, car ils ouvrent la porte à l' injection
JGH
En fait, ces scripts JS et PHP ne sont appelés qu'en interne pour charger des couches dans une carte. Les scripts php ne sont exécutés que si un est connecté. Mais vous avez le droit. N'importe qui, qui est connecté, pourrait facilement manipuler la chaîne GET pour exécuter un sql malveillant. Je vais étudier le lien et voir comment je peux empêcher l'injection. Merci d'avoir regardé le code.
geom
1
Envie de réinventer la roue, pourquoi ne pas simplement utiliser WFS?
nmtoken