import { Style , StyleSet } from "@here/harp-datasource-protocol" ; import { FeaturesDataSource , MapViewFeature , MapViewMultiPolygonFeature } from "@here/harp-features-datasource" ; import { GeoCoordinates , sphereProjection } from "@here/harp-geoutils" ; import { MapControls , MapControlsUI } from "@here/harp-map-controls" ; import { CopyrightElementHandler , MapView } from "@here/harp-mapview" ; import { VectorTileDataSource } from "@here/harp-vectortile-datasource" ; import * as THREE from "three" ; import { apikey } from "../config" ; import { COUNTRIES } from "../resources/countries" ; /**
 * This example illustrates how to add user polygons in [[MapView]]. As custom features, they are
 * handled through a [[FeaturesDataSource]].
 *
 * First we create a base map. For more details, check the `hello` example.
 * ```typescript
 * [[include:harp_demo_features_polygons_0.ts]]
 * ```
 *
 * Then we generate a custom [[StyleSet]] for the countries, with a color gradient based on the
 * year that the country joined the EU. The [[Style]]s are all an [[ExtrudedPolygonStyle]] with only
 * a variation in the color.
 * ```typescript
 * [[include:harp_demo_features_polygons_1.ts]]
 * ```
 *
 * In order to have the extrusion animation for all the individual sets of countries joining, we
 * group them and handle them in separate datasources, so that the various datasources' tiles will
 * get animated independently and highlight new territories. The following snippet highlights the
 * core part of the application's logic: we create these individual [[FeaturesDataSource]] for each
 * set of countries, and create the [[MapViewMultiPolygonFeature]]s for the countries.
 * ```typescript
 * [[include:harp_demo_features_polygons_2.ts]]
 * ```
 *
 * The rest of the code is example-dependent logic to handle the countries sets, with the joining
 * and leaving mechanism, that adds and removes datasources.
 */ export namespace PolygonsFeaturesExample { const EU = getEuropeMemberStatesPerYear ( ) ; const steps = Object . keys ( EU . steps ) ; const stepsNumber = steps . length ; const { styleSet , colorRamp } = generateStyleSet ( { property : "joiningDate" , thresholds : steps . map ( stringYear => Number ( stringYear ) ) , color : "#77ccff" } ) ; const map = createBaseMap ( ) ; const addPromises = [ ] ; const dateButtons = [ ] ; const displayedDataSourceCache = [ ] ; let currentStep = 0 ; const featuresDataSources = generateDataSources ( ) ; let intervalID ; const dateDelay = 2500 ; Promise . all ( addPromises ) . then ( ( ) => { intervalID = setInterval ( setDate , dateDelay ) ; featuresDataSources . forEach ( ds => map . removeDataSource ( ds ) ) ; } ) ; setCaption ( ) ; function getJoiningDate ( j ) { let joiningDate = steps . find ( year => EU . steps [ year ] . joining . includes ( j ) ) ! ; const actualDate = EU . steps [ joiningDate ] . actualJoining ; if ( actualDate !== undefined ) { joiningDate = actualDate ; } return joiningDate ; } function generateDataSources ( ) { const datasources = [ ] ; for ( let j = 0 ; j < EU . statesGroup . length ; j ++ ) { const stateGroup = EU . statesGroup [ j ] ; const features = [ ] ; let age = steps . length ; let k = 0 ; while ( ! EU . steps [ steps [ k ] ] . joining . includes ( j ) ) { age -- ; k ++ ; } if ( stateGroup . includes ( "germany" ) ) { age = steps . length ; } for ( const country of stateGroup ) { const feature = new MapViewMultiPolygonFeature ( COUNTRIES [ country ] , { name : country , joiningDate : getJoiningDate ( j ) , height : 25000 + age * 25000 } ) ; features . push ( feature ) ; } const featuresDataSource = new FeaturesDataSource ( { name : `member-states-${ j }` , styleSetName : "geojson" , features , maxGeometryHeight : 300000 } ) ; const addPromise = map . addDataSource ( featuresDataSource ) ; addPromises . push ( addPromise ) ; datasources . push ( featuresDataSource ) ; } return datasources ; } function setDate ( ) { dateButtons . forEach ( button => button . classList . remove ( "date-active" ) ) ; dateButtons [ currentStep ] . classList . add ( "date-active" ) ; const dataSourcesToShow = [ ] ; const dataSourcesToReShow = [ ] ; if ( currentStep === 0 ) { while ( displayedDataSourceCache . length ) { map . removeDataSource ( displayedDataSourceCache [ 0 ] ) ; displayedDataSourceCache . shift ( ) ; } } for ( const step of steps ) { if ( step !== steps [ currentStep ] ) { dataSourcesToReShow . push ( ... EU . steps [ step ] . joining ) ; for ( const leaving of EU . steps [ step ] . leaving ) { if ( dataSourcesToReShow . includes ( leaving ) ) { dataSourcesToReShow . splice ( dataSourcesToReShow . indexOf ( leaving ) , 1 ) ; } } } else { dataSourcesToShow . push ( ... EU . steps [ step ] . joining ) ; for ( const leaving of EU . steps [ step ] . leaving ) { if ( dataSourcesToReShow . includes ( leaving ) ) { dataSourcesToReShow . splice ( dataSourcesToReShow . indexOf ( leaving ) , 1 ) ; } if ( dataSourcesToShow . includes ( leaving ) ) { dataSourcesToShow . splice ( dataSourcesToShow . indexOf ( leaving ) , 1 ) ; } } break ; } } dataSourcesToReShow . forEach ( value => { const datasource = featuresDataSources [ value ] ; if ( ! displayedDataSourceCache . includes ( datasource ) ) { map . addDataSource ( datasource ) ; displayedDataSourceCache . push ( datasource ) ; } } ) ; dataSourcesToShow . forEach ( value => { const datasource = featuresDataSources [ value ] ; if ( ! displayedDataSourceCache . includes ( datasource ) ) { map . addDataSource ( datasource ) ; displayedDataSourceCache . push ( datasource ) ; } } ) ; displayedDataSourceCache . forEach ( ( datasource , i ) => { let exists = false ; dataSourcesToShow . forEach ( value => { if ( featuresDataSources [ value ] === datasource ) { exists = true ; } } ) ; dataSourcesToReShow . forEach ( value => { if ( featuresDataSources [ value ] === datasource ) { exists = true ; } } ) ; if ( ! exists ) { displayedDataSourceCache . splice ( i , 1 ) ; map . removeDataSource ( datasource ) ; } } ) ; currentStep = ( currentStep + 1 ) % stepsNumber ; } function setCaption ( ) { const caption = document . getElementById ( "dates-container" ) as HTMLDivElement ; let timeoutID ; for ( let j = 0 ; j < steps . length ; j ++ ) { const step = steps [ j ] ; const dateButton = document . createElement ( "button" ) ; dateButton . className = "date" ; dateButton . textContent = step ; dateButton . style . backgroundColor = colorRamp [ j ] ; dateButtons . push ( dateButton ) ; dateButton . onclick = ( ) => { for ( let k = 0 ; k < steps . length ; k ++ ) { if ( steps [ k ] === dateButton . textContent ) { currentStep = k ; break ; } } clearInterval ( intervalID ) ; clearTimeout ( timeoutID ) ; setDate ( ) ; timeoutID = setTimeout ( ( ) => { intervalID = setInterval ( setDate , dateDelay ) ; } , dateDelay ) ; } ; caption . appendChild ( dateButton ) ; } } function generateStyleSet ( options ) { const styles = [ ] ; const colorStrings = [ ] ; const length = options . thresholds . length ; for ( let i = 0 ; i < length ; i ++ ) { const color = new THREE . Color ( options . color ) ; color . multiplyScalar ( ( length - i ) / 2.5 / length + 0.6 ) ; const colorString = "#" + color . getHexString ( ) ; colorStrings . push ( colorString ) ; const max = options . thresholds [ i ] ; const min = i - 1 < 0 ? 0 : options . thresholds [ i - 1 ] ; const propertyName = options . property ; const style = { description : "geoJson property-based style" , technique : "extruded-polygon" , when : [ "all" , [ "==" , [ "geometry-type" ] , "Polygon" ] , [ ">" , [ "to-number" , [ "get" , propertyName ] ] , min ] , [ "<=" , [ "to-number" , [ "get" , propertyName ] ] , max ] ] , attr : { color : colorString , transparent : true , opacity : 0.8 , boundaryWalls : false , constantHeight : true , lineWidth : 1 , lineColor : "#003344" , emissive : colorString , emissiveIntensity : 0.45 } , renderOrder : 1000 } ; styles . push ( style ) ; } return { styleSet : styles , colorRamp : colorStrings } ; } function createBaseMap ( ) { document . body . innerHTML += getExampleHTML ( ) ; const canvas = document . getElementById ( "mapCanvas" ) as HTMLCanvasElement ; const mapView = new MapView ( { canvas , projection : sphereProjection , theme : { extends : "resources/berlin_tilezen_night_reduced.json" , definitions : { northPoleColor : { type : "color" , value : "#3a3c3e" } , southPoleColor : { type : "color" , value : "#4a4d4e" } } , styles : { geojson : styleSet , polar : [ { description : "North pole" , when : [ "==" , [ "get" , "kind" ] , "north_pole" ] , technique : "fill" , attr : { color : [ "ref" , "northPoleColor" ] } , renderOrder : 5 } , { description : "South pole" , when : [ "==" , [ "get" , "kind" ] , "south_pole" ] , technique : "fill" , attr : { color : [ "ref" , "southPoleColor" ] } , renderOrder : 5 } ] } } , target : new GeoCoordinates ( 40 , 15 ) , zoomLevel : 3.2 , enableMixedLod : true } ) ; mapView . renderLabels = false ; const controls = new MapControls ( mapView ) ; controls . maxTiltAngle = 50 ; controls . maxZoomLevel = 6 ; const ui = new MapControlsUI ( controls , { projectionSwitch : true } ) ; canvas . parentElement ! . appendChild ( ui . domElement ) ; window . addEventListener ( "resize" , ( ) => mapView . resize ( innerWidth , innerHeight ) ) ; CopyrightElementHandler . install ( "copyrightNotice" , mapView ) ; const baseMap = new VectorTileDataSource ( { baseUrl : "https://vector.hereapi.com/v2/vectortiles/base/mc" , authenticationCode : apikey } ) ; mapView . addDataSource ( baseMap ) ; return mapView ; } function getEuropeMemberStatesPerYear ( ) { const statesGroup = [ [ "france" , "belgium" , "netherlands" , "italy" , "luxembourg" ] , [ "algeria" ] , [ "ireland" , "denmark" ] , [ "greenland" ] , [ "greece" ] , [ "spain" , "portugal" ] , [ "frg" ] , [ "germany" ] , [ "austria" , "sweden" , "finland" ] , [ "estonia" , "latvia" , "lituania" , "poland" , "slovakia" , "hungary" , "czech" , "slovenia" , "cyprus" , "malta" ] , [ "romania" , "bulgaria" ] , [ "croatia" ] , [ "uk" ] ] ; const UE = { "1957" : { joining : [ 0 , 1 , 6 ] , leaving : [ 2 , 3 , 4 , 5 , 7 , 8 , 9 , 10 , 11 , 12 ] } , "1962" : { leaving : [ 1 ] , joining : [ ] } , "1973" : { joining : [ 2 , 3 , 12 ] , leaving : [ ] } , "1981" : { joining : [ 4 ] , leaving : [ ] } , "1985" : { leaving : [ 3 ] , joining : [ ] } , "1986" : { joining : [ 5 ] , leaving : [ ] } , "1990" : { leaving : [ 6 ] , joining : [ 7 ] , actualJoining : "1957" } , "1995" : { joining : [ 8 ] , leaving : [ ] } , "2004" : { joining : [ 9 ] , leaving : [ ] } , "2007" : { joining : [ 10 ] , leaving : [ ] } , "2013" : { joining : [ 11 ] , leaving : [ ] } , "2020" : { joining : [ ] , leaving : [ 12 ] } } ; return { steps : UE , statesGroup } ; } function getExampleHTML ( ) { return ( `
            <style>
                #mapCanvas {
                    top: 0;
                }
                #caption{
                    width: 100%;
                    position: absolute;
                    bottom: 25px;
                    text-align:center;
                    font-family: Arial;
                    color:#222;
                }
                #dates-container{
                    padding:3px
                }
                h1{
                    font-size:15px;
                    text-transform: uppercase;
                }
                h1, .date{
                    padding: 5px 15px;
                    display: inline-block;
                }
                .date{
                    display: inline;
                    border:none;
                    outline:none;
                    transition: all 0.5s ease;
                    background: none;
                    cursor: pointer;
                    font-weight: bold;
                    color: #222;
                    border-bottom: solid 3px rgba(0,0,0,0);
                }
                .date-active{
                    border-bottom: solid 3px rgba(0,0,0,1);
                }
                #caption-bg{
                    display: inline-block;
                    background: rgba(255,255,255,0.8);
                    border-radius: 4px;
                    margin: 0 10px;
                    max-width:calc(100% - 150px);
                }
                #info{
                    color: #fff;
                    width: 80%;
                    text-align: center;
                    font-family: monospace;
                    left: 50%;
                    position: relative;
                    margin: 10px 0 0 -40%;
                    font-size: 15px;
                }
                @media screen and (max-width: 700px) {
                    .date{
                        padding:5px;
                    }
                    h1{
                        padding:0px;
                        margin:5px
                    }
                    #info{
                        font-size:11px;
                    }
                }
            </style>
            <p id=info>This example demonstrates user polygons, with ` + `property-based styling for their color. The height also ` + `directly comes from the feature's "height" property by default. Here both the color ` + `and the height represent the seniority of a state.</p>
            <div id=caption>
                <div id=caption-bg>
                    <h1>Member states of the European Union</h1>
                    <div id=dates-container></div>
                </div>
            </div>
        ` ) ; } }