quentinlampin / ngx-openlayers

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

Custom openlayers loader function #252

Closed packetstracer closed 4 years ago

packetstracer commented 4 years ago

It would be great to give the posibility to provide an openlayers custom loader function to source components (or whereever needed) in order to access gis servers with specific requirements or handle requests in a customize way.

I could do the development if indications given, don't know where to start. I'll try to include the functionality because I need it in my currrent project and will provide the result in here.

Any help would be apreciated.

packetstracer commented 4 years ago

I'm doing a first try and think that can be done by modifying tileLoadFunction in source parameter passed to _register method in source.component.ts before setting the source to openlayers instance. Something like this seems to work with a xyz-source:

  protected _register(source: any) {
    const token = 'my-token';

    // function for AJAX request (this should be part of tileLoadFunction)
    const request = (url, token) => {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            resolve(xhr);
          } else if (xhr.readyState === 4 && xhr.status !== 200) {
            reject(Error(xhr.statusText));
          }
        };
        xhr.open('GET', url);
        xhr.responseType = 'blob';
        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
        xhr.send();
      })
    };

    // openlayers custom load function, should be passed  as a parameter or already set as source.tileLoadFunction
    const tileLoadFunction = (imageTile: any, url) => {
      request(url, token)
        .then((xhr: any) => {
          const resp = xhr.response;
          const imagen = URL.createObjectURL(resp);
          imageTile.getImage().src = imagen;
          URL.revokeObjectURL(resp);
        })
        .catch(() => {
          imageTile.getImage().src = '#';
        })
    }

    if (this.host) {
      // set the function as source.tileLoadFunction
      source.tileLoadFunction = tileLoadFunction;
      this.host.instance.setSource(source);
    }

    if (this.raster) {
      this.raster.sources = [source];
    }
  }

My Idea is to add an @Input property in xyz.component so the function can be passed when using the component as this:

  <aol-layer-tile [visible]="true">
    <aol-source-xyz
      url="https://my-server.com/{z}/{x}/{y}.png"
      [tileLoadFunction]="myCustomTileLoadFunction"
      opaque="false"
      crossOrigin=""
    >
    </aol-source-xyz>
  </aol-layer-tile>

and then set to the source object in one this 2 places:

I also guess, that this functionality could be added to other source components, but do not know which ones should support this kind of functionality.

Any thoughts on this approach?

Thanks

packetstracer commented 4 years ago

Finally I found that the xyz component has a property for that

@Input() tileLoadFunction?: TileLoadFunctionType;

also de geoson component has a property for that

@Input() loader?: ol.FeatureLoader;

but in the second case I needed to acces geojson component openlayers instance property inside de loader function, so I needed to modify slightly de component ngOnInit() method. The resulting code is this for geojson.component.ts

    ngOnInit() {
        // @NOTE loader function is binded to this in order to have access to this.intance when executing the loader function
        this.loader = this.loader.bind(this);
        this.format = new format.GeoJSON();
        this.instance = new source.Vector(this);

        this.host.instance.setSource(this.instance);
    }

I also have to solve some typescript errors due to using the library source code in my Angular 8 aplication.

If you want a pull request or integrate the changes in the library just let me know.