haoliangyu / ngx-leaflet-starter

A soup of Angular and Leaflet
http://haoliangyu.github.io/ngx-leaflet-starter/
MIT License
209 stars 58 forks source link

Cannot read property 'slice' of undefined #34

Closed FarhAnAbuBakar closed 7 years ago

FarhAnAbuBakar commented 7 years ago

hi,

i want to take geojson function to put into my project. when i integrate that function with my module i get this error

image

here is my map.service.ts

import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import { Map } from 'leaflet';
import * as L from 'leaflet.vectorgrid';

@Injectable()
export class MapService{
  public map: Map;
  private vtLayer: any;

  constructor(private http: Http){}

  toggleGeoJson() {
      if (this.vtLayer) {
          this.map.removeLayer(this.vtLayer);
          delete this.vtLayer;
      } else {
          this.http.get("./app/components/maps/data/airports.geojson")
              .map(res => res.json())
              .subscribe(result => {
                  this.vtLayer = L.vectorGrid.slicer(result);
                  this.vtLayer.addTo(this.map);
              });
      }
    }
}

map.components.ts

import {Component, ElementRef, ViewEncapsulation} from '@angular/core';
import { MapService } from './map.service';

import 'leaflet-map';

@Component({
  encapsulation: ViewEncapsulation.None,
  selector: 'maps',
  templateUrl: './app/components/maps/maps.component.html',
  styleUrls: [ './app/components/maps/maps.components.css' ],
  providers: [MapService]
})
export class MapsComponent {

  geoJsonLayeradded: boolean;

  constructor(private _elementRef:ElementRef, private mapService: MapService ) {
  }

  ngOnInit(): void {
    console.log('map component');
  }

  toggleGeoJson(){
      this.geoJsonLayeradded = !this.geoJsonLayeradded;
      this.mapService.toggleGeoJson();
  }

  ngAfterViewInit() {
    let el = this._elementRef.nativeElement.querySelector('.leaflet-maps');

    L.Icon.Default.imagePath = 'image/';

    var map = L.map(el).setView([3.1561, 101.7140], 13);

    L.tileLayer('https://gios.jupem.gov.my/tms/gios-streets/{z}/{x}/{y}@2x.png', {
      attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    var drawnItems = new L.FeatureGroup();
       map.addLayer(drawnItems);
       var drawControl = new L.Control.Draw({
           edit: {
               featureGroup: drawnItems
           },
       });
       map.addControl(drawControl);

    map.on('draw:created', function (e) {
    map.addLayer(e.layer);
  });

    // L.marker([51.5, -0.09]).addTo(map)
    //   .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
    //   .openPopup();

  }
}

map.html

<ba-card title="Leaflet Maps">
      <div class="leaflet-maps"></div>
      <button (click)="toggleGeoJson()">get geoJson<button>
    </ba-card>

any suggestion how to solve it??

FarhAnAbuBakar commented 7 years ago

i already change "slice" to "slicer" and the problem still same

image

haoliangyu commented 7 years ago

It looks like a problem of angular. So I try to update the dependencies and rebuild the project. It works without any error at both FF 52 and Chrome 57 for me. Please test with the latest master branch.

Because of a Leaflet.VectorGrid bug (#94), the vector tile slicing of Point geojson will throw errors. Please use it with LineString or Polygon geojson.

FarhAnAbuBakar commented 7 years ago

this is my dependencies and i still encounter same error

{
  "name": "ifosfield",
  "productName": "ifosfield",
  "version": "1.0.0",
  "description": "My Electron application description",
  "main": "src/index.ts",
  "scripts": {
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make",
    "lint": "tslint src"
  },
  "keywords": [],
  "author": "Huzaifah",
  "license": "MIT",
  "config": {
    "forge": {
      "make_targets": {
        "win32": [
          "squirrel"
        ],
        "darwin": [
          "zip"
        ],
        "linux": [
          "deb",
          "rpm"
        ]
      },
      "electronPackagerConfig": {},
      "electronWinstallerConfig": {
        "name": "ifosfield"
      },
      "electronInstallerDebian": {},
      "electronInstallerRedhat": {},
      "github_repository": {
        "owner": "",
        "name": ""
      },
      "windowsStoreConfig": {
        "packageName": "",
        "name": "ifosfield"
      }
    }
  },
  "dependencies": {
    "@angular/common": "^2.4.1",
    "@angular/compiler": "^2.4.1",
    "@angular/core": "^2.4.1",
    "@angular/flex-layout": "^2.0.0-rc.1",
    "@angular/forms": "^2.4.9",
    "@angular/http": "^2.4.9",
    "@angular/material": "^2.0.0-beta.2",
    "@angular/platform-browser": "^2.4.1",
    "@angular/platform-browser-dynamic": "^2.4.1",
    "@angular/router": "^3.4.9",
    "@asymmetrik/angular2-leaflet": "^1.3.2",
    "@asymmetrik/angular2-leaflet-draw": "^2.0.0",
    "@swimlane/ngx-datatable": "^6.3.0",
    "@types/electron": "^1.4.30",
    "@types/geojson": "1.0.1",
    "@types/leaflet-draw": "^0.4.3",
    "angular-oauth2-oidc": "^1.0.20",
    "@types/leaflet": "^1.0.60",
    "angular2-datatable": "^0.5.3",
    "angular2-material-datepicker": "^0.5.0",
    "electron-compile": "^6.1.3",
    "electron-devtools-installer": "^2.0.1",
    "file-saver": "^1.3.3",
    "hammerjs": "^2.0.8",
    "leaflet": "^1.0.3",
    "leaflet-draw": "^0.4.9",
    "leaflet-map": "0.2.1",
    "leaflet.vectorgrid": "^1.2.0",
    "lodash": "^4.17.4",
    "mangol": "^0.2.8",
    "material-components-web": "^0.6.0",
    "material-design-icons": "^3.0.1",
    "ng2-bootstrap": "^1.6.3",
    "ngx-perfect-scrollbar": "^4.0.0",
    "openlayers": "^4.0.1",
    "pouchdb": "^6.1.2",
    "reflect-metadata": "^0.1.9",
    "rxjs": "^5.2.0",
    "tslib": "^1.4.0",
    "typing": "^0.1.9",
    "zone.js": "^0.7.4"
  },
  "devDependencies": {
    "@types/file-saver": "0.0.0",
    "@types/leaflet": "^1.0.56",
    "babel-plugin-transform-async-to-generator": "^6.22.0",
    "babel-preset-env": "^1.2.1",
    "babel-preset-react": "^6.23.0",
    "electron-prebuilt-compile": "1.4.15",
    "tslint": "^4.2.0",
    "typescript": "~2.1.4"
  }
}
haoliangyu commented 7 years ago

In your map.service.ts, you can simply import the leaflet.vectorgrid with

import 'leaflet.vectorgrid';

// NOT import * as L from 'leaflet.vectorgrid', because it overwrites the global "L" variable
// This is the reason why "L.vectorGrid" was undefined.

Also don't forget to import the type declaration file for leaflet.vectorgrid.

FarhAnAbuBakar commented 7 years ago

in map.service.ts i already import the leaflet.vectorgrid as well as at bootstrap.ts

/// <reference path="../typings/index.d.ts" />
/// <reference path="./typings/require.d.ts"/>
/// <reference path="./typings/leaflet.vectorgrid.d.ts"/>

import './polyfills.ts';
import './vendor.ts';
import "leaflet";
import "leaflet.vectorgrid";

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppModule }  from './app/app.module';

// @NgModule({
//   providers: [MapService],
// })

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

but the error still there.. i noticed that error was related with rxjs/subscriber.ts.. i also import

rxjs/subscription with

import { Subscribe } from 'rxjs/Subscription';

image

after i import subscribe and the error still not gone... any ideas what the root cause of this error?

haoliangyu commented 7 years ago

Right, but the in the map.service.ts code you first shared, the leaflet.vectorgrid was imported in a wrong way:

import * as L from 'leaflet.vectorgrid';  // this should be deleted

This would overwrite the global variable L. Do you still have the error after deleting this row?

FarhAnAbuBakar commented 7 years ago

yess... i already deleted that row and replace with

import 'leaflet.vectorgrid';
}

and declare variable L at

leaflet.vectorgird.d.ts

declare namespace L {
    namespace vectorGrid {
        export function slicer(data: any, options?: any): any;
    }
}

and import that file into file

bootstrap.ts

/// <reference path="../typings/index.d.ts" />
/// <reference path="./typings/require.d.ts"/>
/// <reference path="./typings/leaflet.vectorgrid.d.ts"/>

import './polyfills.ts';
import './vendor.ts';
import "leaflet";
import "leaflet.vectorgrid";

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
import { AppModule }  from './app/app.module';

// @NgModule({
//   providers: [MapService],
// })

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

after i done this and still getting this error.

haoliangyu commented 7 years ago

Hmm, sorry, I have no idea why it still throws such bug now :-(

FarhAnAbuBakar commented 7 years ago

i try to slice the result manually until get coordinates value and my code like this

map.service.ts

import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import {LeafmapCoreComponent} from "../shared/leafmap/core/core.component";
import {Map} from "leaflet";
import 'leaflet.vectorgrid';
import 'geojson-vt';

@Injectable()
export class MapService{
  private vtLayer: any;
  private map: Map;

  constructor(private http: Http){}

  toggleGeoJson() {
      if (this.vtLayer) {
          this.map.removeLayer(this.vtLayer);
          delete this.vtLayer;
      } else {
          this.http.get("https://rawgit.com/haoliangyu/angular2-leaflet-starter/master/public/data/airports.geojson")
              .map(res => res.json())
              .subscribe(result => {

                  this.vtLayer = result.features[0].geometry.coordinates;
                  this.vtLayer.addTo(this.map);
              });

      }
    }
}

but i get this error _this.vtLayer.addTo is not a function

image

any ideas or my method is wrong?

haoliangyu commented 7 years ago

In your code, this.vtLayer should be an array of numbers. If you want to add a point with the coordinates, you could try

// see http://leafletjs.com/reference-1.0.3.html#marker
L.marker([this.vtLayer[1], this.vtLayer[0]]).addTo(this.map);
FarhAnAbuBakar commented 7 years ago

oohhh...this method only can display one point and in my project it need to display multiple point like your project

haoliangyu commented 7 years ago

Do you actually need vector tile? If not, you can simply use a for loop to add all points:

for (let point of result.features) {
  let coords = point.geometry.coordinates;
  L.marker([coords[1], coords[0]]).addTo(this.map);
}
FarhAnAbuBakar commented 7 years ago

yess.. i need a vector tile instead of marker

FarhAnAbuBakar commented 7 years ago

i already can slice the result and get this value after slice

image

and get this error "cannot read property addLayer of undefined"

image

i try to import leaflet module that contain addLayer function but its still not working

haoliangyu commented 7 years ago

Which variable is undefined? Do you check if the map variable in the map.service.ts is initialized (see app.component.ts in this project)?

FarhAnAbuBakar commented 7 years ago

i initialized map variable at another folder.

core.component.ts

import { Component } from '@angular/core';

import * as L from 'leaflet';

import { LeafmapCoreModel } from './core.model';

@Component({
    selector: 'leafmapCore',
    templateUrl: './core.component.html'
})
export class LeafmapCoreComponent {

    /*
     * This is a specification of the leaflet options
     * The reason to duplicate this object is so we can easily render it to the template
     */
    optionsSpec: {
        layers: any[],
        zoom: number,
        center: number[]
    } = {
        layers: [
            {
                url: 'https://gios.jupem.gov.my/tms/gios-streets/{z}/{x}/{y}@2x.png',
                maxZoom: 18,
                attribution: 'Open Cycle Map'
            },
            {
                url: 'https://gios.jupem.gov.my/tms/gios-streets/{z}/{x}/{y}@2x.png',
                maxZoom: 18,
                attribution: 'Open Street Map'
            },
        ],
        zoom: 5,
        center: [ 46.879966, -121.726909 ]
    };

    // Fields for managing the form inputs and binding to leaflet zoom/center
    model = new LeafmapCoreModel(
        this.optionsSpec.center[0],
        this.optionsSpec.center[1],
        this.optionsSpec.zoom
    );
    zoom: number;
    center: L.LatLng;

    /*
     * This are the leaflet map options that we're going to use for input binding
     */

    options = {
        layers: this.optionsSpec.layers.map((l) => {
            return L.tileLayer(l.url, { maxZoom: l.maxZoom, attribution: l.attribution });
        }),
        zoom: this.optionsSpec.zoom,
        center: L.latLng({ lat: this.optionsSpec.center[0], lng: this.optionsSpec.center[1] })
    };

    fitBoundsOptions = {
        padding: 100,
        maxZoom: 10,
        animate: true,
        duration: 1
    };

    panOptions = {
        animate: true,
        duration: 1
    };

    zoomOptions = {
        animate: true,
        duration: 1
    };

    zoomPanOptions = {
        animate: true,
        duration: 1
    };

    onApply() {
        this.zoom = this.model.zoom;
        this.center = L.latLng([ this.model.latitude, this.model.longitude]);

        return false;
    }

}

and import all component into leafmap.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { LeafletModule } from '@asymmetrik/angular2-leaflet/src/leaflet/leaflet.module';

import { LeafmapComponent } from './leafmap.component';
import { LeafmapCoreComponent } from './core/core.component';

@NgModule({
    imports: [
        CommonModule,
        FormsModule,

        LeafletModule
    ],
    declarations: [
        LeafmapComponent,
        LeafmapCoreComponent
    ],
    exports: [
        LeafmapComponent
    ],
    bootstrap: [ LeafmapComponent ],
    providers: [ ]
})
export class LeafmapModule { }

and then call into leafmap.module.ts into map.service.ts and declare leafmap as map variable

import {Injectable} from "@angular/core";
import {LeafmapCoreComponent} from "../shared/leafmap/core/core.component";
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import {OAuthService} from "angular-oauth2-oidc";
import { Observable } from 'rxjs/Rx';
import {Map} from "leaflet";
import {LeafmapModule} from "../shared/leafmap/leafmap.module";
import 'rxjs/add/operator/map';
import 'leaflet.vectorgrid';
import 'geojson-vt';

@Injectable()
export class MapService{
  private vtLayer: any;
  private map: Map;
  private leafmap: LeafmapModule;

  constructor(private http: Http, private oauthService: OAuthService){}

  ngOnInit(): any{
  }

  toggleGeoJson() {
      if (this.vtLayer) {
          this.map.removeLayer(this.vtLayer);
          delete this.vtLayer;
      } else {
          this.http.get("https://rawgit.com/haoliangyu/angular2-leaflet-starter/master/public/data/airports.geojson")
              .map(res => res.json())
              .subscribe(result => {
                  console.log(result);
                  // this.vtLayer = result.features[0].geometry.coordinates;
                  this.vtLayer = L.vectorGrid.slicer(result);
                  console.log(this.vtLayer);
                  this.vtLayer.addTo(this.leafmap);
              });

      }
    }
}

sory im still newbies and try to familiar with typescript environment.

haoliangyu commented 7 years ago

To use the xxx.addTo() function, your this.leafmap should be a Leaflet map object, which is created by L.map(). Not a ng2 module. Since you are using angular2-leaflet to create the map, the map object is created internally. Note sure if angular2-leaflet can expose its internal map object. If you can do that, then you can use this.vtLayer.addTo(the_map_object);. Otherwise, you need to find a way to inject that vector tile layer into the angular2-leaflet module.

Noted that my project is using vanilla Leaflet, not any Angular Leaflet module, to create map.

FarhAnAbuBakar commented 7 years ago

when i do like this my showing perfectly

<div id="map"><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
    <navigator></navigator>
    <toolbar></toolbar>
  </div>

when i do like this

<div id="map"><br>
    <navigator></navigator>
    <toolbar></toolbar>
  </div>

my map looking like this

image

if i delete whole <br> map doesn't come out... something wrong with my code?

haoliangyu commented 7 years ago

I think you need to specify the height of your map div. In this repo, I did this.

FarhAnAbuBakar commented 7 years ago

errox fixed... i use getJSON function instead of vectorgrid.slicer.. this code worked for me

$.getJSON("./geojson/GeoJson_Files/Laluan_Ampang_Ukay_Perdana.geojson",
           function(data:any) {
              var geojson = L.geoJSON(data);
              geojson.addTo(map);
       });