Como não havia respostas para minhas perguntas, tentei me inspirar em outro código já existente e continuei desenvolvendo um construtor GeoJSON básico nos openlayers 3, que atendem às minhas necessidades.
Eu realmente posso
- carregue camadas diferentes do PostGIS passando um nome de tabela, campos de tabela e uma cláusula WHERE (com toda a magia possível do PosgreSQL / PostGis)
- estilize-os (usando valores únicos, únicos ou de intervalo)
- mostrar / ocultar camadas em min / maxResolution
- identificador exclusivo precisa ser id para impedir que os recursos sejam carregados várias vezes
- as camadas são carregadas por meio da estratégia ol.loadingstrategy.bbox para que apenas os dados visíveis sejam carregados
- definir modelos pop-up específicos da camada
- adicionar rotulagem específica de camada
Portanto, já é um pacote divertido, que pode ser adaptado (e melhorado) com muita facilidade. Portanto, quero compartilhar o código aqui. O todo consiste em três partes:
- ol3Vector.js uma classe estendida de ol.layer.Vector
- um arquivo map.js no qual o mapa e todas as camadas são definidas
- O arquivo get_geojson.php usado no lado do servidor para criar a string sql e retornar GeoJSON válido.
Para funcionar corretamente, o ol3Vector.js precisa ser carregado primeiro. Aqui está o código:
// 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);
aqui um exemplo do 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);
Finalmente, precisamos do get_geojson.php para buscar dados do banco de dados 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);
?>
Diverta-se! Isso pode ajudar alguém a ir mais longe ou melhorar esse código e ser publicado no GitHub.