Per rappresentare i dati geografici degli studenti si `e pensato di mettere a disposizione dell’utente una mappa con metodi di visualizzazione e categorie di dati intercambiabili, selezionabili da appositi componenti select:
• modalit`a di visualizzazione: controlla la tipologia di rappresentazione dei dati utilizzata, la quale pu`o essere:
– marker: ciascuna posizione `e rappresentata da un’icona circolare; se le posizioni sono troppo vicine tra loro, relativamente al gra- do di zoom corrente della mappa, le icone delle singole posizioni verranno sostituite da un’unica icona di maggiore circonferenza e riportante il numero delle posizioni che comprende (cluster). – heatmap: le posizioni vengono rappresentate sotto forma di mappa
di calore; i colori saranno pi`u intensi nelle aree con un numero di posizioni maggiore.
– quartieri di Bologna: viene rappresentata una mappa coropleti- ca nell’area relativa ai quartieri di Bologna; l’intensit`a del colore del quartiere `e proporzionale alla quantit`a delle posizioni al suo interno rispetto al totale.
• tipo di posizione: controlla quale categoria di dati geografici deve essere rappresentata (luogo di nascita, residenza o domicilio).
• parametro di visualizzazione: selezionabile solo in modalit`a marker, assegna un colore diverso alle icone in base al valore della propriet`a selezionata dello studente (distanza dalla sede di corso, corso di laurea, anno di corso e altre categorie di dati analoghe a quelle memorizzate nel database); nel caso un cluster comprenda posizioni relative a studenti con valori diversi, avr`a il colore di default.
• secondo parametro di visualizzazione: permette di assegnare un colore diverso alle icone per ciascuna combinazione esistente di valori della
3.3 Sviluppo della mappa 53
seconda propriet`a selezionata con valori della prima propriet`a; appare solo a primo parametro di visualizzazione selezionato e con propriet`a diversa da “distanza dalla sede di corso”.
Il componente che racchiude tutto questo `e MapPanel : comprende il
componente WorldMap , quattro ReactResponsiveSelect e MapLegend . Per suddividere lo spazio dei componenti, sono stati usati utilizzati dei sistemi a griglia di Bootstrap: nei viewport con larghezza maggiore di 576 pixel ( sm ) la mappa occupa 3/4 dell’elemento contenitore e il restante 1/4 `e occupato dai tre select e dalla legenda, mentre in viewport con larghezza minore o uguale a 576px le due parti occupano l’intera larghezza con la mappa sopra il resto. In quest’ultimo caso, inoltre, il componente MapLegend appare come una finesta modale visualizzabile cliccando su un apposita checkbox.
Figura 3.1: Il risultato della renderizzazione del componente MapPanel. MapPanel prende in input tramite props le informazioni degli studenti, dei quartieri, dei corsi di laurea e dei parametri dei select da rendere seleziona- ti al termine del montaggio dei componenti, e le assegna allo stato per gestire le loro eventuali modifiche. La modifica dello stato pu`o avvenire sia trami-
Figura 3.2: Il risultato della renderizzazione del componente MapPanel su risoluzione mobile, rispettivamente con legenda abilitata e disabilitata.
te la chiamata della funzione updateWorldMapState() da parte di App , che a seguito della selezione in uno dei quattro select a disposizione. Con- giuntamente, verranno richiamate tramite refs le funzioni updateState() dei componenti figli WorldMap e MapLegend atte ad aggiornare lo stato di questi ultimi con le nuove informazioni.
WorldMap restituisce un componente ReactMapGL che, oltre ad ave- re come propriet`a informazioni sulla viewport, sull’accesso e al caricamento della mappa, ha tra i figli un NavigationControl fornito dalla libreria e un componente dipendente dalla modalit`a di visualizzazione selezionata e quindi memorizzata nello stato.
In modalit`a marker ( this.state.mode === "markers" ), verr`a rende- rizzato un componente Cluster [74][75] che racchiude e renderizza un com- ponente Marker per ciascuno studente rappresentabile, e che fa uso della
3.3 Sviluppo della mappa 55
libreria Supercluster per accorpare in un unico Marker con propriet`a diver- se le posizioni vicine tra loro in base alle propriet`a impostate (livelli di zoom minimo e massimo a cui viene generato il cluster, raggio di azione, ecc.).
Ciascun componente Marker ha come propriet`a le coordinate della po-
sizione a esso associata, e ha come figlio un componente Pin, che consiste in un’icona circolare a cui viene assegnato un colore e una funzione attivata dopo un click su di essa.
Generalmente il colore di un Pin `e calcolato utilizzando come riferimento le stringhe dei valori dei parametri di visualizzazioni selezionati. Nel caso il parametro selezionato sia la distanza dalla sede di corso, viene utilizzata la funzione scaleLinear() offerta dalla libreria D3 per regolare l’intensit`a del colore in base al valore dell’elemento, calcolato utilizzando una funzione che restituisce la distanza geografica in chilometri tra la posizione del marker e quella della sede del corso che lo studente associato frequenta (memorizzata in this.state.degreesSeats ).
Il colore dei Pin associati ai cluster `e calcolato in modo analogo utilizzan- do come riferimento il primo degli studenti in esso compresi, ma solamente se tutti i valori del parametro degli studenti sono uguali; in caso contrario, verr`a utilizzato il colore di default. Per colore di default si intende il colore generato utilizzando come parametro la stringa "all" .
Sia Cluster che Marker e Pin estendono la classe React.PureComponent :
a differenza dei componenti normali, implementano il metodo shouldComponentUpdate() con una comparazione “shallow” delle props e dello state, ovvero confrontan-
do gli oggetti correnti con quelli nuovi solamente tramite chiavi univoche e non nel loro contenuto. Quando si ha la certezza che il risultato della renderizzazione di un componente sia sempre lo stesso a partire dagli stessi valori di props e state, `e conveniente utilizzare questa classe per avere un miglioramento delle performance in alcuni casi.[76]
Nella funzione di render, viene renderizzato anche il contenuto di this.state.popup , inizialmente null e modificato al click su un componente Marker assegnan-
do allo stato un componente Popup . 1 h a n d l e M a r k e r C l i c k ( student , p o p u p S t r i n g s ) { 2 let p o p u p P a r a m e t e r s C o u n t = p o p u p S t r i n g s . f i l t e r ( p o p u p = > p o p u p . p a r a m e t e r S t r i n g ) 3 . map ( p o p u p = > p o p u p . p a r a m e t e r S t r i n g ) 4 . r e d u c e (( a , c ) = > { a [ c ] = ( a [ c ] || 0) + 1; r e t u r n a } , {}) ; 5 6 if ( s t u d e n t && s t u d e n t [ t h i s . s t a t e . t y p e ] && p o p u p S t r i n g s . l e n g t h > 0) { 7 t h i s . s e t S t a t e ({ 8 p o p u p : < P o p u p c l a s s N a m e ={ s t y l e s . p o p u p + " " + s t y l e s . f a d e I n } 9 t i p S i z e = { 5 } 10 a n c h o r =" bottom - r i g h t " 11 l o n g i t u d e ={ s t u d e n t [ t h i s . s t a t e . t y p e ]. l o n g i t u d e } 12 l a t i t u d e ={ s t u d e n t [ t h i s . s t a t e . t y p e ]. l a t i t u d e } 13 o n C l o s e = { ( ) = > t h i s . s e t S t a t e ({ p o p u p : n u l l }) } 14 c l o s e O n C l i c k ={ t r u e } > 15 < R e a c t . F r a g m e n t > 16 < div c l a s s N a m e ={ s t y l e s . a d d r e s s } >{ p o p u p S t r i n g s [ 0 ] . a d d r e s s S t r i n g } </ div > 17 < div c l a s s N a m e ={ s t y l e s . p a r a m e t e r L i s t } > 18 { O b j e c t . k e y s ( p o p u p P a r a m e t e r s C o u n t ) . map (( pp , key ) = >
19 < div key ={" p a r a m e t e r -"+ key } c l a s s N a m e ={
s t y l e s . p a r a m e t e r } > 20 { pp + (( p o p u p P a r a m e t e r s C o u n t [ pp ] > 1) ? (" (" + p o p u p P a r a m e t e r s C o u n t [ pp ] + ") ") : "") } 21 </ div > 22 ) } 23 </ div > 24 </ R e a c t . F r a g m e n t > 25 </ Popup > 26 }) ;
3.3 Sviluppo della mappa 57
27 }
28 }
Per popupStrings si intende un array di oggetti col formato 1 {
2 a d d r e s s S t r i n g : String , 3 p a r a m e t e r S t r i n g : S t r i n g 4 }
ricavato con la seguente funzione:
1 g e t P o p u p S t r i n g s ( s t u d e n t s , d e g r e e s S e a t s , type , p a r a m e t e r , p a r a m e t e r 2 ) { 2 let p o p u p S t r i n g s = []; 3 s t u d e n t s . f o r E a c h ( s t u d e n t = > { 4 let a d d r e s s S t r i n g = ""; 5 let p a r a m e t e r S t r i n g = ""; 6 if ( s t u d e n t [ t y p e ]) { 7 if ( t y p e === " d o m i c i l e " && s t u d e n t [ t y p e ]. s t r e e t ) { 8 a d d r e s s S t r i n g += ( s t u d e n t [ t y p e ]. s t r e e t + " ") 9 } 10 if ( t y p e === " d o m i c i l e " && s t u d e n t [ t y p e ]. n u m b e r ) { 11 a d d r e s s S t r i n g += ( s t u d e n t [ t y p e ]. n u m b e r + " , ") 12 } 13 if ( s t u d e n t [ t y p e ]. m u n i c i p a l i t y ) { 14 a d d r e s s S t r i n g += s t u d e n t [ t y p e ]. m u n i c i p a l i t y 15 } 16 } 17 if ( p a r a m e t e r === " s e a t D i s t a n c e " && s t u d e n t [ t y p e ] && s t u d e n t [ t y p e ]. l a t i t u d e && s t u d e n t [ t y p e ]. l o n g i t u d e ) { 18 p a r a m e t e r S t r i n g += ( g e t S e a t D i s t a n c e ( student , type , d e g r e e s S e a t s ) +" km ") 19 } e l s e if ( p a r a m e t e r && p a r a m e t e r !== " n o n e " && s t u d e n t [ p a r a m e t e r ]) { 20 p a r a m e t e r S t r i n g += s t u d e n t [ p a r a m e t e r ] 21 } 22 if ( p a r a m e t e r 2 === " s e a t D i s t a n c e " && s t u d e n t [ t y p e ] && s t u d e n t [ t y p e ]. l a t i t u d e && s t u d e n t [ t y p e ]. l o n g i t u d e ) {
23 p a r a m e t e r S t r i n g += (" - " + g e t S e a t D i s t a n c e ( student , type , d e g r e e s S e a t s ) +" km ") 24 } e l s e if ( p a r a m e t e r 2 && p a r a m e t e r 2 !== " n o n e " && s t u d e n t [ p a r a m e t e r 2 ]) { 25 p a r a m e t e r S t r i n g += (" - " + s t u d e n t [ p a r a m e t e r 2 ]) 26 } 27 p o p u p S t r i n g s . p u s h ({ 28 a d d r e s s S t r i n g : a d d r e s s S t r i n g , 29 p a r a m e t e r S t r i n g : p a r a m e t e r S t r i n g 30 }) 31 }) 32 r e t u r n p o p u p S t r i n g s ; 33 }
In quanto `e possibile che esistano cluster relativi a pi`u posizioni con le stesse identiche coordinate (ad esempio la stessa citt`a), si `e ritenuto necessa- rio gestire la comparsa di un popup anche per i cluster che si trovano in questa situazione: viene riutilizzato il codice della funzione handleMarkerClick() , usando come riferimento per le coordinate la prima posizione nell’array delle posizioni degli studenti. Indipendentemente da ci`o, un click su un cluster por- ta a uno zoom della mappa per visualizzare i marker delle posizioni singole, se possibile. 1 h a n d l e C l u s t e r C l i c k ( c l u s t e r P r o p s ) { 2 if ( c l u s t e r P r o p s . p o i n t s . l e n g t h === 1) { 3 let c l u s t e r S t u d e n t s = t h i s . g e t C l u s t e r S t u d e n t s ( c l u s t e r P r o p s ) ; 4 let p o p u p S t r i n g s = t h i s . g e t P o p u p S t r i n g s ( c l u s t e r S t u d e n t s , t h i s . s t a t e . d e g r e e s S e a t s , t h i s . s t a t e . type , t h i s . s t a t e . p a r a m e t e r , t h i s . s t a t e . p a r a m e t e r 2 ) 5 if ( c l u s t e r S t u d e n t s . l e n g t h > 0 && p o p u p S t r i n g s . l e n g t h > 0) { 6 t h i s . h a n d l e M a r k e r C l i c k ( c l u s t e r S t u d e n t s [0] , p o p u p S t r i n g s ) 7 } 8 } 9
3.3 Sviluppo della mappa 59 10 if ( t h i s . s t a t e . v i e w p o r t . z o o m < t h i s . s t a t e . v i e w p o r t . m a x Z o o m ) { 11 t h i s . s e t S t a t e ( p r e v S t a t e = > ({ v i e w p o r t : { 12 ... p r e v S t a t e . v i e w p o r t , 13 l o n g i t u d e : c l u s t e r P r o p s . c l u s t e r . g e o m e t r y . c o o r d i n a t e s [0] , 14 l a t i t u d e : c l u s t e r P r o p s . c l u s t e r . g e o m e t r y . c o o r d i n a t e s [1] , 15 z o o m : ( p r e v S t a t e . v i e w p o r t . z o o m + 2 < p r e v S t a t e . v i e w p o r t . m a x Z o o m ) ? ( p r e v S t a t e . v i e w p o r t . z o o m + 2) : ( t h i s . s t a t e . v i e w p o r t . m a x Z o o m ) 16 }}) ) 17 } 18 }
clusterProps.points `e un array delle posizioni distinte rappresentate nel cluster: ci`o vuol dire che se un cluster rappresenta 30 posizioni con le
stesse identiche coordinate, clusterProps.points avr`a una lunghezza pari
a 1.
Nel caso la modalit`a selezionata sia heatmap ( this.state.mode === "heatmap" ),
verr`a semplicemente renderizzato un componente HeatmapOverlay della li-
breria react-map-gl-heatmap-overlay che prende in ingresso le coordina- te delle posizioni del tipo selezionato e le informazioni di viewport.
Per quanto riguarda la modalit`a relativa ai quartieri di Bologna
( this.state.mode === "choropleth" ), verr`a renderizzato un componen- te ChoroplethOverlay , che prende in ingresso l’insieme degli oggetti GeoJ- SON Feature relativi ai quartieri memorizzati in this.state.neighborhoods per rappresentare le rispettive figure poligonali. Ciascun poligono `e riempito da un colore con intensit`a dipendente dall’attributo properties.value del- la Feature, assegnato durante l’aggiornamento dello stato e che rappresenta il numero di posizioni comprese nell’area del quartiere:
1 t h i s . s e t S t a t e ({
3 n e i g h b o r h o o d s : ( n e i g h b o r h o o d s ) ? n e i g h b o r h o o d s . map (( f e a t u r e ) = > { 4 c o n s t f = f e a t u r e ; 5 f . p r o p e r t i e s . v a l u e = ( t y p e ) 6 ? ( s t u d e n t s . f i l t e r (( s t u d e n t ) = > 7 ( s t u d e n t [ t y p e ] && 8 s t u d e n t [ t y p e ]. l o n g i t u d e && s t u d e n t [ t y p e ]. l a t i t u d e && 9 g e o C o n t a i n s ( feature , [ s t u d e n t [ t y p e ]. l o n g i t u d e , s t u d e n t [ t y p e ]. l a t i t u d e ]) ) ) . l e n g t h ) 10 : 0; 11 r e t u r n f ; 12 }) : n e i g h b o r h o o d s , 13 ... 14 })
geoContains `e una funzione fornita dalla libreria D3 che restituisce un valore booleano true se la feature passata come primo parametro comprende il punto con le coordinate passate nel secondo parametro, altrimenti false .
MapLegend `e un componente che mostra una legenda dei colori utiliz-
zati nella mappa, visibile in modalit`a “Marker” e “Quartieri di Bologna”. Viene utilizzato un sistema a griglia Bootstrap, con una colonna per i Pin rappresentanti i colori e una per il rispettivo valore.
3.3 Sviluppo della mappa 61
Figura 3.3: Il risultato della renderizzazione del componente MapPanel selezionando il parametro relativo alla distanza dalla sede di corso.
Figura 3.4: Un possibile risultato della renderizzazione del componente
Figura 3.5: Il risultato della renderizzazione del componente MapPanel in modalit`a heatmap.
Figura 3.6: Il risultato della renderizzazione del componente MapPanel in modalit`a quartieri di Bologna.