quentinlampin / ngx-openlayers

Angular2+ components for Openlayers 4.x
Mozilla Public License 2.0
137 stars 97 forks source link

How to capture a mouse over event on a <aol-feature> with OpenLayer #261

Open pascal101 opened 4 years ago

pascal101 commented 4 years ago

Is there a way to capture mouse over event on a aol-feature on a OSM map ? I have a device list (points) which are dysplayed on the map and my goal is to be able to show a pop-up once the mouse is over a device (aol-feature). I'm beginner on OpenLayer librairie and on OSM map.

First I tried to follow this explaination with onClick event : https://github.com/quentin-ol/ngx-openlayers/issues/84 But it does not work for me.

I'm using Angular 7 with : bootstrap: 4.1.3, ngx-bootstrap: 5.6.1, ngx-openlayers : 0.8.22, ol: 6.4.3.

Here is my template code :

<aol-map [logo]="true" [width]="width" [height]="height" (onClick)="onClick($event)">
    <aol-interaction-default></aol-interaction-default>
    <aol-view [zoom]="zoom">
        <aol-coordinate [x]="longitude" [y]="latitude" [srid]="'EPSG:4326'"></aol-coordinate>
    </aol-view>
    <aol-layer-tile [opacity]="opacity">
        <aol-source-osm></aol-source-osm>
    </aol-layer-tile>
    <aol-layer-vector [opacity]="opacity">
        <aol-source-vector>    
            <aol-feature *ngFor="let device of devices; let i = index" (click)="setPopupContent(device)" [id]="i">                  
                <aol-geometry-point>
                    <aol-coordinate [x]="device.gpsLongitude" [y]="device.gpsLatitude" [srid]="'EPSG:4326'">
                    </aol-coordinate>
                </aol-geometry-point>
                <aol-style>
                    <aol-style-circle [radius]="10">
                        <aol-style-stroke [color]="'black'" [width]="width" ></aol-style-stroke>
                        <aol-style-fill [color]="'green'"></aol-style-fill>
                    </aol-style-circle>
                </aol-style>
            </aol-feature>
            <aol-overlay #popup *ngIf="popupContent.shown"> 
                   <aol-coordinate [x]="popupContent.gpsLongitude" [y]="popupContent.gpsLatitude" [srid]="'EPSG:4326'">      
                   </aol-coordinate>
                   <aol-content>
                        <div><p>hi</p></div>
                   </aol-content>
            </aol-overlay>
        </aol-source-vector>
    </aol-layer-vector>

Here is the typescript code :

import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'
import { GeoLocationService } from 'src/app/common/services/geo-location.service'
import { marker } from './marker.image'
import * as olProj from 'ol/proj'
import { View } from 'ol'
import { HttpClient } from '@angular/common/http'
import { Device } from '../../common/model/device'
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { MapBrowserEvent, Feature } from 'ol';

@Component({
  selector: 'app-osm-view',
  templateUrl: './osm-view.component.html',
  styleUrls: ['./osm-view.component.css'],
  providers: [HttpClient, GeoLocationService]
})
export class OsmViewComponent implements OnInit {

    public features: Device[];
    public popupContent: { shown: boolean; feature: Device};

    constructor(private httpClient: HttpClient, 
            private geoLocationService: GeoLocationService,
            private reportManagementService: ReportManagementService
            ) {
        this.features = [];
        this.popupContent = {
          shown: false,
          feature: undefined
        };

    }

    public onClick(event: MapBrowserEvent) {
        console.log("onClick : this.event : ", event);
        console.log("onClick : this.devices : ", this.devices);
        event.map.forEachFeatureAtPixel(event.pixel, (feature: Feature) => {
          let featureId: number;
          featureId = feature.deviceId;
          console.log("onClick : feature.getId() : ", feature.deviceId);
          console.log("onClick : this.features[featureId] : ", this.features[featureId]);
          this.setPopupContent(this.features[featureId]);
        });
      }

      public setPopupContent(feature: Device) {
        this.popupContent.shown = true;
        this.popupContent.feature = feature;
        console.log("In setPopupContent method : this.popupContent.shown : ", this.popupContent.shown);
        console.log("In setPopupContent method : this.popupContent.feature : ", this.popupContent.feature);
      }

You can see in the browser console the result of my console.log trace

onClick : this.event :  Object { type: "click", target: {…}, map: {…}, frameState: {…}, originalEvent: pointerdown, pixel: (2) […], coordinate: (2) […], dragging: false, b: {…} }
onClick : this.devices :  Array [ {…}, {…} ]
onClick : feature.getId() :  undefined
onClick : this.features[featureId] :  undefined
In setPopupContent method : this.popupContent.shown :  true
In setPopupContent method : this.popupContent.feature :  undefined

So when I click on a device, the event is captured by no popup is displayed. What is the problem ?

How to modify the code if I would like to do this on mouse over event ? I tried this (mouseover)="mouseover($event)" but it does not work.

Please help. Thank you in advance...

wflemingnz-retired commented 4 years ago

Based on the code you have given, you aren't setting popupContent.gpsLongitude and popupContent.gpsLatitude so the overlay will not be positioned hence you won't see the popup.

Regarding mouseover, you can do it by handling the map pointermove event and calling map.forEachFeatureAtPixel(..) once to get the feature.

pascal101 commented 4 years ago

Thank you very much for your answer.

I made some changes in my code like you can see below. Now a popup appears on click event on a DEVICE. But it is always at the bottom left of the screen, and quite far from the clicked point, as you can see on the image below.

Popup_out_of_map

I don't understand why.

First, I updated my Angular framework to the 10 version and librairies. I also updated librairies like ol, bootstrap, ngx-openlayers In my package.json :

 "dependencies": {
    "@angular/animations": "^10.1.2",
    "@angular/common": "^10.1.2",
    "@angular/compiler": "^10.1.2",
    "@angular/core": "^10.1.2",
    "@angular/forms": "^10.1.2",
    "@angular/localize": "^10.1.2",
    "@angular/platform-browser": "^10.1.2",
    "@angular/platform-browser-dynamic": "^10.1.2",
    "@angular/platform-server": "^10.1.2",
    "@angular/router": "^10.1.2",
    "@ng-bootstrap/ng-bootstrap": "^7.0.0",
    "@popperjs/core": "^2.5.1",
    "angular-font-awesome": "^3.1.2",
    "bootstrap": "^4.5.2",
    "core-js": "^3.6.5",
    "ngx-bootstrap": "^6.1.0",
    "ngx-openlayers": "^0.8.22",
    "ol": "^6.4.3",
    "popper.js": "^1.16.1",
    "rxjs": "^6.6.3",
  },

Here is my new template code :

<aol-map [logo]="true" [width]="width" [height]="height" (onClick)="onClick($event)">
    <aol-interaction-default></aol-interaction-default>
    <aol-view [zoom]="zoom">
        <aol-coordinate [x]="longitude" [y]="latitude" [srid]="'EPSG:4326'"></aol-coordinate>
    </aol-view>
    <aol-layer-tile [opacity]="opacity">
        <aol-source-osm></aol-source-osm>
    </aol-layer-tile>
    <aol-layer-vector [opacity]="opacity">
        <aol-source-vector>

            <aol-feature *ngFor="let device of devices" (click)="setPopupContent(device) [id]="device.deviceId">                    
                <aol-geometry-point>
                    <aol-coordinate [x]="device.gpsLongitude" [y]="device.gpsLatitude" [srid]="'EPSG:4326'">
                    </aol-coordinate>
                </aol-geometry-point>
                <aol-style>
                    <aol-style-circle [radius]="10">
                        <aol-style-stroke [color]="'black'" [width]="width" ></aol-style-stroke>
                        <aol-style-fill [color]="'green'"></aol-style-fill>                     
                    </aol-style-circle>
                </aol-style>
            </aol-feature>

            <aol-overlay *ngIf="popupContent.shown"> 
                   <aol-coordinate [x]="popupContent.feature.gpsLongitude" [y]="popupContent.feature.gpsLatitude" [srid]="'EPSG:4326'">      
                   </aol-coordinate>
                   <aol-content>
                       <div id="popup" class="ol-popup">
                          <a href="#" id="popup-closer" class="ol-popup-closer"></a>
                          <div id="popup-content">DEVICE : gpsLongitude {{ popupContent.feature.gpsLongitude }} / gpsLatitude {{ popupContent.feature.gpsLatitude }}</div>
                        </div>
                   </aol-content>
            </aol-overlay>
        </aol-source-vector>
    </aol-layer-vector>

Here is the new typescript code :

import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'
import { GeoLocationService } from 'src/app/common/services/geo-location.service'
import { marker } from './marker.image'
import * as olProj from 'ol/proj'
import { HttpClient } from '@angular/common/http'
import { Subscription } from 'rxjs'
import { Device } from '../../common/model/device'
import { ReportManagementService } from 'src/app/common/services/report-management.service'
import { MapBrowserEvent, Feature } from 'ol';
import { Map } from 'ol/Map';
import { View } from 'ol/View';
import { TileLayer } from 'ol/layer/Tile';
import { XYZ } from 'ol/source/XYZ';
import { NgbdTooltipBasicModule } from 'src/app/shared/tooltip/tooltip.module'
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { Overlay, OverlayPositioning} from 'ol/Overlay';
import { toLonLat } from 'ol/proj';
import { toStringHDMS } from 'ol/coordinate';

@Component({
  selector: 'app-osm-view',
  templateUrl: './osm-view.component.html',
  styleUrls: ['./osm-view.component.css'],
  providers: [HttpClient, GeoLocationService]
})
export class OsmViewComponent implements OnInit {

    public features: Device[];
    public popupContent: { shown: boolean; feature: Device};

    constructor(private httpClient: HttpClient, 
            private geoLocationService: GeoLocationService,
            private reportManagementService: ReportManagementService
            ) {
        this.features = [];
        this.popupContent = {
          shown: false,
          feature: undefined
        };
    }

    public onClick(event: MapBrowserEvent) {
        event.map.forEachFeatureAtPixel(event.pixel, (feature: Feature) => {
            console.log("onClick : event.pixel : ", event.pixel);
            console.log("onClick : event : ", event);

          let featureId: number;
          featureId = feature.getId();
          console.log("onClick : feature : ", feature);
          console.log("onClick : feature.getId() : ", feature.getId());

          let device =  this.devices.find(x => x.deviceId === featureId);
          console.log("onClick : device : ", device);

          this.setPopupContent(device);
        });
      }

      public setPopupContent(feature: Device) {
        this.popupContent.shown = true;
        this.popupContent.feature = feature;
        console.log("In setPopupContent method : this.popupContent.shown : ", this.popupContent.shown);
        console.log("In setPopupContent method : this.popupContent.feature : ", this.popupContent.feature);
      }

You can see in the browser console the result of my console.log trace

onClick : event.pixel :  Array [ 628, 844.4000244140625 ]
onClick : event :  Object { type: "click", target: {…}, map: {…}, frameState: {…}, originalEvent: pointerdown, pixel: (2) […], coordinate: (2) […], dragging: false, b: {…} }
onClick : feature :  Object { Ua: {}, sa: {}, ra: {…}, f: 5, cp: 30, O: {…}, c: 57, a: "geometry", g: {…}, j: b(), … }
onClick : feature.getId() :  57
onClick : device :  Object { deviceId: 57, gpsLatitude: 45.71499557422359, gpsLongitude: 4.806758317260741, reports: [], trackingSystem: null, sensorCharacteristic: (1) […], sensorType: (1) […], active: true, visible: false }
In setPopupContent method : this.popupContent.shown :  true
In setPopupContent method : this.popupContent.feature :  Object { deviceId: 57, gpsLatitude: 45.71499557422359, gpsLongitude: 4.806758317260741, reports: [], trackingSystem: null, sensorCharacteristic: (1) […], sensorType: (1) […], active: true, visible: false }

Here is the styles I got from https://openlayers.org/en/latest/examples/popup.html

 <style>
      .map {
        width: 100%;
        height:400px;
      }
      .ol-popup {
        position: absolute;
        background-color: white;
        box-shadow: 0 1px 4px rgba(0,0,0,0.2);
        padding: 15px;
        border-radius: 10px;
        border: 1px solid #cccccc;
        bottom: 12px;
        left: -50px;
        min-width: 280px;
      }
      .ol-popup:after, .ol-popup:before {
        top: 100%;
        border: solid transparent;
        content: " ";
        height: 0;
        width: 0;
        position: absolute;
        pointer-events: none;
      }
      .ol-popup:after {
        border-top-color: white;
        border-width: 10px;
        left: 48px;
        margin-left: -10px;
      }
      .ol-popup:before {
        border-top-color: #cccccc;
        border-width: 11px;
        left: 48px;
        margin-left: -11px;
      }
      .ol-popup-closer {
        text-decoration: none;
        position: absolute;
        top: 2px;
        right: 8px;
      }
      .ol-popup-closer:after {
        content: "✖";
      }
    </style>

1) So the popup is displayed but outside the map. What is the problem, how to correct that ?

2) As you advised me regarding mouseover, i tried the map pointermove event ( (mouseover)="mouseover($event)" ) and I had "TypeError: event.map is undefined" once my mouse is on the map.

3) I tried a little test with mouseover event but OUTSIDE the map and it works perfectly :

    <p (mouseover)="mouseover()" placement="top" tooltip="Tooltip on top"><b>Mouseover</b> here</p>
    <h1>{{counter}} </h1>

Please help. Thank you in advance...

wflemingnz-retired commented 4 years ago
  1. Hard to say without running example(ie: stackblitz) and further investigation. Probably either a projection issue or an html layout issue
  2. Use the openlayers pointermove event rather than mouseover: <aol-map (pointermove)="pointerMoved()">