Skip to content

Instantly share code, notes, and snippets.

@comeetie
Last active February 5, 2019 10:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save comeetie/7888580 to your computer and use it in GitHub Desktop.
Save comeetie/7888580 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>@comeetie : carte données carroyées</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.4.5/leaflet.js"></script>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.4.5/leaflet.css" />
<link href='http://fonts.googleapis.com/css?family=Inder' rel='stylesheet' type='text/css'>
<script src="http://d3js.org/d3.v3.min.js"></script>
<!--[if lte IE 8]><link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.4.5/leaflet.ie.css" /><![endif]-->
<style type="text/css">
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
}
.legend {
line-height: 18px;
color: #555;
}
.legend i {
width: 12px;
height: 12px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
.info {
padding: 6px 8px;
font: 14px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: #777;
}
#menu {
padding: 15px 15px;
z-index :20;
position: absolute;
top: 2%;
right: 2%;
height : 180px;
width : 165px;
}
#splash {
padding: 15px 15px;
z-index :30;
position: absolute;
top: 2%;
left: 25%;
width:600px;
font-family: "Inder",Arial, Helvetica,sans-serif;
font-size:16px;
}
#infos {
padding: 15px 15px;
z-index :50;
position: absolute;
top: 250px;
right: 2%;
width: 165px;
}
svg{
position:relative;
}
path {
fill: none;
fill-opacity: 0.6;
stroke: #000;
stroke-width: 0px;
}
path:hover {
fill-opacity: 1;
cursor: pointer;
stroke: red;
stroke-width: 1px;
}
.axis{
font-size:10px;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.vname h4{
padding:0px;
margin:0px;
}
.myButton {
background-color:#053061;
-moz-border-radius:3px;
-webkit-border-radius:3px;
border-radius:3px;
border:1px solid #29668f;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:arial;
font-size:17px;
padding:5px 20px;
text-decoration:none;
text-shadow:0px 1px 0px #3d768a;
}
.myButton:hover {
background-color:#4393c3;
}
.myButton:active {
position:relative;
top:1px;
}
</style>
<script>
$(function() {
$( "#slider" ).slider({
value:0.6,
min: 0,
max: 1,
step: 0.05,
slide: function( event, ui ) {
clayer.setOpacity(ui.value);
d3.select(map.getPanes().overlayPane).selectAll("svg").selectAll("path").style("fill-opacity",ui.value)
}
});
});
</script>
</head>
<body>
<div id="map"></div>
<div id="menu" class="info legend"></div>
<div id="infos" class="info legend"><h4>Informations détaillées</h4></div>
<div id="splash" class="info legend">
<h1>La france en 2 278 213 pixels &#9632;</h1>
Cette carte permet de visualiser certaines données issuent des <a href="Revenus fiscaux localisés des ménages">revenus fiscaux localisés des ménages</a> de 2010. Ces données sont effet de nouveau disponibles de manière très fine (<a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">aggréger sur des pixels de 200m par 200m ! </a>).</br></br> J'ai donc essayé de visualiser celles-ci à différentes échelles : aux échelles hautes, les données sont aggrégées sur des pixels de 4km&sup2;, aux échelles intermédiaires sur des pixels de 1km&sup2; et enfin aux échelles fines sur des pixels de 200m par 200m. A ces échelles fines, les informations détaillées associées à chaque pixels sont aussi disponibles par simple survol. N'hésitez donc pas à zoomer pour explorer les détails et à jouer avec les différentes variables. Les différentes variables que j'ai choisi de représenter sont :
<ul>
<li> la densité de population (nombre d'habitants / km&sup2;);</li>
<li> le pourcentage d'habitants de moins de 25 ans;</li>
<li> le pourcentage d'habitants de plus de 65 ans;</li>
<li> le revenu moyen par <a href="http://www.insee.fr/fr/methodes/default.asp?page=definitions/unite-consommation.htm" target="_blank">unité de consomation</a>;</li>
<li> le pourcentage de ménages à <a href="http://www.insee.fr/fr/methodes/default.asp?page=definitions/pauvrete-monetaire.htm">bas revenus</a> (c’est à dire inférieur à 60 % de la médiane des revenus par unité de consommation avant impôt observés au niveau national).</li>
</ul>
</br></br><div style="text-align:center"><a href="#" class="myButton" onclick="d3.select('#splash').style('display','none')">Laissez moi explorer</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#" class="myButton" onclick="zoom()">Zoomer sur ma position</a>
</div>
</br></br>
Sources : <a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">Données carroyées (INSEE)</a></br>
Outils : <a href="http://www.r-project.org/">R</a>,<a href="https://www.mapbox.com/tilemill/">TileMill</a>, <a href="http://www.d3js.org"> d3</a>
</div>
<script>
var map;
// mapbox street
//https://{s}.tiles.mapbox.com/v3/comeetie.gf56e3b2/{z}/{x}/{y}.png
//mapbox terrain
//https://{s}.tiles.mapbox.com/v3/comeetie.ggf4aib9/{z}/{x}/{y}.png
var MapBackground = new L.TileLayer("https://{s}.tiles.mapbox.com/v3/comeetie.gf56e3b2/{z}/{x}/{y}.png", {
minZoom : 6,
maxZoom : 14,
attribution: '<a href="https://www.mapbox.com/about/maps/">MapBox Terms & Feedback</a>, <a href="http://www.openstreetmap.org/copyright">© Contributeurs de OpenStreetMap.</a>',
unloadInvisibleTiles:true
});
var CarreauxPop = new L.TileLayer("http://www.comeetie.fr/mbtiles-php/CarreauxPopShpSmall/{z}/{x}/{y}.png", {
minZoom : 6,
maxZoom : 14,
attribution: '<a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">INSEE</a>',
opacity : 0.7
});
var CarreauxRevenus = new L.TileLayer("http://www.comeetie.fr/mbtiles-php/CarreauxRevShpSmall/{z}/{x}/{y}.png", {
minZoom : 6,
maxZoom : 14,
attribution: '<a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">INSEE</a>',
opacity : 0.7,
});
var CarreauxBasslr = new L.TileLayer("http://www.comeetie.fr/mbtiles-php/CarreauxBslrShpSmall/{z}/{x}/{y}.png", {
minZoom : 6,
maxZoom : 14,
attribution: '<a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">INSEE</a>',
opacity : 0.7
});
var CarreauxJeunes = new L.TileLayer("http://www.comeetie.fr/mbtiles-php/CarreauxJeunesShpSmall/{z}/{x}/{y}.png", {
minZoom : 6,
maxZoom : 14,
attribution: '<a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">INSEE</a>',
opacity : 0.7
});
var CarreauxVieux = new L.TileLayer("http://www.comeetie.fr/mbtiles-php/CarreauxVieuxShpSmall/{z}/{x}/{y}.png", {
minZoom : 6,
maxZoom : 14,
attribution: '<a href="http://www.insee.fr/fr/themes/detail.asp?reg_id=0&ref_id=donnees-carroyees&page=donnees-detaillees/donnees-carroyees/donnees-carroyees-200m.htm">INSEE</a>',
opacity : 0.7,
});
var clayer = CarreauxRevenus;
var clayername="revenus"
map = new L.Map("map",{layers: [MapBackground,CarreauxRevenus], center: [48.843961,2.479023], zoom: 7});
var baseMaps = {
"Minimal": MapBackground
};
function showPosition(position){
map.panTo([position.coords.latitude, position.coords.longitude]);
}
var zoom = function(){
if (navigator.geolocation){
navigator.geolocation.getCurrentPosition(showPosition);
d3.select("#splash").style("display","none");
}
map.setZoom(11);
}
var overlayMaps = {
"dpop": CarreauxPop,
"jeunes": CarreauxJeunes,
"vieux": CarreauxVieux,
"revenus": CarreauxRevenus,
"basslr": CarreauxBasslr,
};
var colScales ={
"vieux":{ colscale :
{"0":"#003C30", "0.0555555555555556":"#01665E", "0.0838574423480084":"#35978F", "0.106276984390568":"#80CDC1", "0.1270207852194":"#C7EAE5", "0.14828897338403":"#F6E8C3", "0.171578947368421":"#DFC27D", "0.198808234019502":"#BF812D", "0.235294117647059":"#8C510A", "0.294117647058824":"#543005", "0.798816568047337":"#fff"},
unit : "%",
subtitle :" % de la population residante agée de + de 65 ans.",
fullname : "% de 65 ans et plus"},
"dpop": { colscale :{"25":"#FFFFCC", "375":"#FFEDA0", "925":"#FED976", "1675":"#FEB24C", "2762.5":"#FD8D3C", "4562.5":"#FC4E2A", "7912.5":"#E31A1C", "15400":"#B10026", "109600":"#fff"},
//{ "25":"#FFFFCC","50":"#FFEDA0","75":"#FED976","100":"#FEB24C","150":"#FD8D3C","250":"#FC4E2A","500":"#E31A1C","1200":"#B10026","10000":"#fff"},
unit : "hab/km&sup2;",
subtitle :"Densité de population",
fullname: "Densité de population"},
"jeunes": { colscale :
{"0":"#A50026", "0.202380952380952":"#D73027", "0.238666167103784":"#F46D43", "0.263843648208469":"#FDAE61", "0.285714285714286":"#FEE08B", "0.305199698568199":"#D9EF8B", "0.325428194993412":"#A6D96A", "0.347826086956522":"#66BD63", "0.374429223744292":"#1A9850", "0.411764705882353":"#006837", "0.813953488372093":"#fff"},
unit : "%",
subtitle :" % de la population residante agée de - de 25 ans.",
fullname : "% de moins de 25 ans"},
"revenus": { colscale :
{"7555.79032258064":"#67001F", "13939.1951807229":"#B2182B", "15787.1666666667":"#D6604D", "17049.5850931677":"#F4A582", "18105.5505617978":"#FDDBC7", "19069.3488372093":"#D1E5F0", "20038.8492462312":"#92C5DE", "21081.4113712375":"#4393C3", "22287.7197837838":"#2166AC", "23882.091954023":"#053061", "29297.2142857143":"#fff"},
unit : "&euro;",
subtitle:"Moyenne des revenus fiscaux par unité de consommation.",
fullname :"Revenus"},
"basslr": { colscale :
{"0":"#1A1A1A", "0.0434782608695652":"#4D4D4D", "0.075":"#878787", "0.103448275862069":"#BABABA", "0.133333333333333":"#E0E0E0","0.166666666666667":"#FDDBC7","0.2":"#F4A582",
"0.244493392070485":"#D6604D", "0.302752293577982":"#B2182B", "0.4":"#67001F", "0.799485861182519":"#fff"},
unit : "%",
subtitle: "% de ménages dont le revenu fiscal par unité de consommation est en dessous du seuil de bas revenu.",
fullname: "% de bas revenus"
},
};
upInfos =function(d){
infos="";
for (p in d.properties){
if (p!="idINSPIRE"){
val =d.properties[p];
if(colScales[p].unit=="%"){
val=val*100;
}
infos+="<h4 class='vname'>"+colScales[p].fullname+"</h4>" + d3.round(val,2) +" "+colScales[p].unit+ " </br>"
}
}
infos+=""
d3.select("#infos").html(infos);
}
cols=function(layer){
var s = colScales[layer].colscale;
var lsd=[];
for (index in s){ lsd.push({value: +index, color: s[index]})};
lsd = lsd.sort(function(a,b){ return b.value-a.value})
var ccol=function(x){
var cs=lsd.filter(function(c){return x >= c.value});
if(cs.length>0){
return(cs[0].color)
}else{
return "#fff";
}
}
return ccol;
}
var ccolor=cols(clayername);
loadTile=function(e){
surl=e.url.split("/")
z=surl[surl.length-3];
if(z>=10){
x=surl[surl.length-2];
y=surl[surl.length-1].split(".")[0];
vtileurl="./vtiles/"+z+"/"+surl[surl.length-2]+"/"+y+".geojson";
var svg = d3.select(map.getPanes().overlayPane).append("svg").attr("id","vt"+x+y+z),
g = svg.append("g").attr("class", "leaflet-zoom-hide");
d3.json(vtileurl, function(error, data) {
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform)
var feature = g.selectAll("path")
.data(data.features)
.enter().append("path");
map.on("viewreset", reset);
reset();
// Reposition the SVG to cover the features.
function reset() {
bounds = path.bounds(data)
var topLeft = bounds[0],
bottomRight = bounds[1];
svg.attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
var opa= $('#slider').slider("option", "value")
feature.attr("d",path).style("fill",function(d){return ccolor(d.properties[clayername]);})
.on("mouseover",function(d){upInfos(d)})
.on("mouseout",function(d){d3.select("#infos")
.style("fill-opacity",opa)
.html("<h4>Information détaillées</h4>");});
}
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
});
}
}
clearTile=function(e){
if(e.url!=undefined){
console.log("remove")
surl=e.url.split("/")
z=surl[surl.length-3];
x=surl[surl.length-2];
y=surl[surl.length-1].split(".")[0];
d3.select("#vt"+x+y+z).remove()
}
}
clearAllTiles=function(e){
console.log("clear")
d3.select(map.getPanes().overlayPane).selectAll("svg").remove()
console.log("clearend")
}
selmenu = function () {
innerHTML = "<h4>Opacité : </h4><div id=\"slider\"></div><br><h4>Variable : </h4>"
for (ov in overlayMaps){
if(clayer==overlayMaps[ov]){
innerHTML +=
'<input type="radio" name="grpvar" value="'+ov+'" onClick="changeOverlay(\''+ov+'\')" checked>' +
colScales[ov].fullname + '</input><br>'
}else{
innerHTML +=
'<input type="radio" name="grpvar" value="'+ov+'" onClick="changeOverlay(\''+ov+'\')">' +
colScales[ov].fullname + '</input><br>'
}
}
return innerHTML;
};
$( "#menu" ).html(selmenu());
var legend = L.control({position: 'bottomleft'});
legend.onAdd = function(map){
var div = L.DomUtil.create('div', 'info legend');
div.setAttribute("id","colscale")
return div;
}
legend.addTo(map);
var lwidth = 200, lheight=100, lmargin=18;
d3.select("#colscale").append("h4").attr("id","legendtitle")
var svg = d3.select("#colscale").append("svg").attr("width",lwidth+2*lmargin).attr("height",lheight+2*lmargin)
var gl= svg.append("g").attr("transform", "translate(" + lmargin + "," + lmargin + ")");
drawlegend = function (layer){
var s = colScales[layer].colscale;
var lsd=[];
for (index in s){ if(colScales[layer].unit=="%"){lsd.push({value: +index*100, color: s[index]})}else{lsd.push({value: +index, color: s[index]})}};
lsd.sort(function(a,b){ return a.value-b.value})
console.log(lsd)
rect=[];
for (i =0; i<(lsd.length-1);i++){
rect.push({"x": lsd[i].value,"width": lsd[i+1].value-lsd[i].value,"height":1/(lsd[i+1].value-lsd[i].value),"col":lsd[i].color})
}
console.log(rect)
x = d3.scale.linear().domain([d3.min(rect,function(r){return r.x}), d3.max(rect,function(r){return r.x+r.width})]).range([0, lwidth])
w = d3.scale.linear().domain([0 , d3.max(rect,function(r){return r.x+r.width})-d3.min(rect,function(r){return r.x})]).range([0, lwidth])
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(4);
y = d3.scale.linear().domain([0, d3.max(rect,function(r){return r.height})]).range([0, lheight])
gl.selectAll("g").remove()
gl.append("g").attr("class", "bars").selectAll("rect").data(rect).enter().append("rect")
.attr("x",function(d){return x(d.x)})
.attr("width", function(d){return Math.max(w(d.width),1)})
.attr("y", function(d){return lheight-y(d.height)})
.attr("height", function(d){return Math.max(y(d.height),1)})
.style("fill", function(d){return d.col})
gl.append("g").attr("class", "x axis").attr("transform", "translate(0," + lheight + ")").call(xAxis);
d3.select("#legendtitle").html(colScales[layer].fullname + " (" +colScales[layer].unit +") ")
}
drawlegend(clayername)
clayer.setOpacity(0.6);
changeOverlay = function(layer){
map.removeLayer(clayer);
map.addLayer(overlayMaps[layer]);
clayername=layer
clayer=overlayMaps[layer];
console.log(layer)
ccolor=cols(layer);
var opa = $('#slider').slider("option", "value");
clayer.setOpacity(opa);
d3.select(map.getPanes().overlayPane).selectAll("svg").selectAll("path")
.style("fill",function(d){return ccolor(d.properties[layer]);})
.style("fill-opacity",opa)
drawlegend(layer)
}
MapBackground.addEventListener('tileload',loadTile)
MapBackground.addEventListener('tileunload',clearTile)
map.on("zoomstart",clearAllTiles)
</script>
</body>
</html>
library(rgdal)
library(foreign)
library(sp)
library(maptools)
library(raster)
# lecture et mise en forme des tables carreaux et rectangle
car = read.dbf("../200m-carreaux-metropole/car_m.dbf")
rect= read.dbf("../200m-rectangles-metropole/rect_m.dbf")
car$id=as.character(car$id)
car$idk=as.character(car$idk)
car$idINSPIRE=as.character(car$idINSPIRE)
rect$idk=as.character(rect$idk)
CAR=merge(car,rect[,c('idk','ind_srf','men_basr','ind_age7','ind_r','men')],by.x='idk',by.y='idk',all.x=T)
# conversion en raster
# recuperation des coordonnées inspire
xy=sapply(CAR$idINSPIRE,function(x){r = strsplit(x,"[N,E]");r=r[[1]];as.numeric(r[3:4])})
xy=t(xy)
xy=xy[,c(2,1)]
xy[,2]=xy[,2]+200;
# creation du raster à partir des x,y,z
# exemple pour la variable men_basr
RR=rasterFromXYZ(cbind(xy,CAR$men_basr))
# specifié la projection
projection(RR)=CRS("+init=epsg:3035")
writeRaster(RR,"basr.tif",overwrite=T)
# creation d'une spatial polygon data frame ! RAM
l=apply(xy,1,function(x){list(coord=x)})
pols=lapply(l,function(x){ x = x$coord;rbind(x[1:2],c(x[1]+200,x[2]),c(x[1]+200,x[2]-200),c(x[1],x[2]-200),x[1:2])})
Pols=lapply(pols,function(x){Polygons(list(Polygon(x)),paste('CRS3035RES200mN',x[4,2],'E',x[4,1],sep=""))})
sp=SpatialPolygons(Pols, proj4string=CRS("+init=epsg:3035"))
spdf=SpatialPolygonsDataFrame(sp,car)
writeOGR(spdf, ".", "data", "ESRI Shapefile")
library(sp)
library(rjson)
# fonction de projection en coords tuile
deg2num = function(lat_deg, lon_deg, zoom){
lat_rad <- lat_deg * pi /180
n <- 2.0 ^ zoom
xtile <- floor((lon_deg + 180.0) / 360.0 * n)
ytile = floor((1.0 - log(tan(lat_rad) + (1 / cos(lat_rad))) / pi) / 2.0 * n)
return( c(xtile, ytile))
}
vectortiles = function(spDF,minzoom,maxzoom,projectname){
dir.create(paste("./",projectname,"/",sep=""), showWarnings = FALSE)
# projeté en lat / long
for (c in 1:length(spDF)){
print(round(c/length(spDF)*1000))
#serialiser la tuile en geojson
# creation du gjson
gjson = "{ \"type\": \"Feature\", \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[";
# append des coords
# [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
pol=spDF@polygons[[c]]
gjson = paste(gjson,toString(apply(pol@Polygons[[1]]@coords,1,toJSON)))
# append des data
gjson = paste(gjson, "] ] }, \"properties\": ");
pr=as.list(spDF@data[c,])
#names(pr)=names(spDF@data)
gjson = paste(gjson,toJSON(pr));
gjson = paste(gjson, " }");
# boucle sur les zoom //
for (z in minzoom:maxzoom){
# récupérer la tuile
# from latlong to x,y
xy=deg2num(mean(pol@Polygons[[1]]@coords[,2]),mean(pol@Polygons[[1]]@coords[,1]),z)
# append de la tuile ds le bon fichier
fname=paste("./",projectname,"/",z,"/",xy[1],"/",xy[2],".geojson",sep="")
if(!file.exists(fname)){
dir.create(paste("./",projectname,"/",z,"/",sep=""), showWarnings = FALSE)
dir.create(paste("./",projectname,"/",z,"/",xy[1],"/",sep=""), showWarnings = FALSE)
write("{ \"type\": \"FeatureCollection\", \"features\": [",fname)
write(gjson,fname,append=T)
}else{
write(paste(",", gjson),fname,append=T)
}
# directement dans un MBtile ?
}
}
# fermeture des tuiles
for (z in minzoom:maxzoom){
X=system(paste('ls ./',projectname,"/",z,"/",sep=""),intern=T)
for (x in X){
Y=system(paste('ls ./',projectname,"/",z,"/",x,"/",sep=""),intern=T)
for (y in Y){
fname=paste("./",projectname,"/",z,"/",x,"/",y,sep="")
write("]}",fname,append=T)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment