Skip to content

Commit

Permalink
feat(map): add LineString and MultiLineString support
Browse files Browse the repository at this point in the history
  • Loading branch information
pissang committed Jan 9, 2022
1 parent 2c7692d commit ae31f39
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 131 deletions.
102 changes: 62 additions & 40 deletions src/component/helper/MapDraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,21 @@ class MapDraw {
const mapOrGeoModel = viewBuildCtx.mapOrGeoModel;
const data = viewBuildCtx.data;

const transformPoint = function (point: number[]): number[] {
function transformPoint(point: number[]): number[] {
return [
point[0] * transformInfoRaw.scaleX + transformInfoRaw.x,
point[1] * transformInfoRaw.scaleY + transformInfoRaw.y
];
};

function transformPoints(inPoints: number[][]): number[][] {
const outPoints = [];
for (let i = 0; i < inPoints.length; ++i) {
outPoints.push(transformPoint(inPoints[i]));
}
return outPoints;
}

regionsGroup.removeAll();

// Only when the resource is GeoJSON, there is `geo.regions`.
Expand All @@ -268,56 +276,70 @@ class MapDraw {
regionsInfoByName.set(regionName, { dataIdx, regionModel });
}

const compoundPath = new graphic.CompoundPath({
segmentIgnoreThreshold: 1,
shape: {
paths: []
const polygonSubpaths: graphic.Polygon[] = [];
const polylineSubpaths: graphic.Polyline[] = [];

zrUtil.each(region.geometries, function (geometry) {
// Polygon and MultiPolygon
if (geometry.type === 'polygon') {
polygonSubpaths.push(new graphic.Polygon({
shape: {
points: transformPoints(geometry.exterior)
}
}));
zrUtil.each(geometry.interiors, (interior) => {
polygonSubpaths.push(new graphic.Polygon({
shape: {
points: transformPoints(interior)
}
}));
});
}
// LineString and MultiLineString
else {
zrUtil.each(geometry.points, points => {
polylineSubpaths.push(new graphic.Polyline({
shape: {
points: transformPoints(points)
}
}));
});
}
});
regionGroup.add(compoundPath);

zrUtil.each(region.geometries, function (geometry) {
if (geometry.type !== 'polygon') {
const centerPt = transformPoint(region.getCenter());

function createCompoundPath(subpaths: graphic.Path[], isLine?: boolean) {
if (!subpaths.length) {
return;
}
const points = [];
for (let i = 0; i < geometry.exterior.length; ++i) {
points.push(transformPoint(geometry.exterior[i]));
}
compoundPath.shape.paths.push(new graphic.Polygon({
const compoundPath = new graphic.CompoundPath({
culling: true,
segmentIgnoreThreshold: 1,
shape: {
points: points
paths: subpaths
}
}));
});
regionGroup.add(compoundPath);
applyOptionStyleForRegion(
viewBuildCtx, compoundPath, dataIdx, regionModel
);
resetLabelForRegion(
viewBuildCtx, compoundPath, regionName, regionModel, mapOrGeoModel, dataIdx, centerPt
);

for (let i = 0; i < (geometry.interiors ? geometry.interiors.length : 0); ++i) {
const interior = geometry.interiors[i];
const points = [];
for (let j = 0; j < interior.length; ++j) {
points.push(transformPoint(interior[j]));
}
compoundPath.shape.paths.push(new graphic.Polygon({
segmentIgnoreThreshold: 1,
shape: {
points: points
}
}));
// Only stroke can be used for line.
// Using fill in style if stroke not exits.
// TODO Not sure yet. Perhaps a separate `lineStyle`?
if (isLine) {
const style = compoundPath.style;
style.stroke = (style.stroke || style.fill);
style.fill = null;
}
});

applyOptionStyleForRegion(
viewBuildCtx, compoundPath, dataIdx, regionModel
);

if (compoundPath instanceof Displayable) {
compoundPath.culling = true;
}

const centerPt = transformPoint(region.getCenter());
resetLabelForRegion(
viewBuildCtx, compoundPath, regionName, regionModel, mapOrGeoModel, dataIdx, centerPt
);
createCompoundPath(polygonSubpaths);
createCompoundPath(polylineSubpaths, true);
});

// Ensure children have been added to `regionGroup` before calling them.
Expand Down Expand Up @@ -726,7 +748,7 @@ function resetLabelForRegion(
el,
getLabelStatesModels(regionModel),
{
labelFetcher: labelFetcher,
labelFetcher,
labelDataIndex: query,
defaultText: regionName
},
Expand Down
151 changes: 80 additions & 71 deletions src/coord/geo/Region.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,49 +25,79 @@ import * as polygonContain from 'zrender/src/contain/polygon';
import { GeoJSON, GeoSVGGraphicRoot } from './geoTypes';
import * as matrix from 'zrender/src/core/matrix';
import Element from 'zrender/src/Element';
import { each } from 'zrender/src/core/util';

const TMP_TRANSFORM = [] as number[];

export class Region {
function transformPoints(points: number[][], transform: matrix.MatrixArray) {
for (let p = 0; p < points.length; p++) {
vec2.applyTransform(points[p], points[p], transform);
}
}

export abstract class Region {

readonly name: string;
readonly type: 'geoJSON' | 'geoSVG';

protected _center: number[];
protected _rect: BoundingRect;

constructor(
name: string
) {
this.name = name;
}

setCenter(center: number[]) {
this._center = center;
}

/**
* Get center point in data unit. That is,
* for GeoJSONRegion, the unit is lat/lng,
* for GeoSVGRegion, the unit is SVG local coord.
*/
getCenter(): number[] {
return;
getCenter() {
let center = this._center;
if (!center) {
// In most cases there are no need to calculate this center.
// So calculate only when called.
center = this._center = this.calcCenter();
}
return center;
}


abstract calcCenter(): number[];
}

export class GeoJSONPolygonGeometry {
readonly type = 'polygon';
exterior: number[][];
interiors?: number[][][];
constructor(exterior: number[][], interiors: number[][][]) {
this.exterior = exterior;
this.interiors = interiors;
}
}
export class GeoJSONLineStringGeometry {
readonly type = 'linestring';
points: number[][][];
constructor(points: number[][][]) {
this.points = points;
}
}

export class GeoJSONRegion extends Region {

readonly type = 'geoJSON';

readonly geometries: {
type: 'polygon'; // FIXME:TS Is there other types?
exterior: number[][];
interiors?: number[][][];
}[];

private _center: number[];
readonly geometries: (GeoJSONPolygonGeometry | GeoJSONLineStringGeometry)[];

// Injected outside.
properties: GeoJSON['features'][0]['properties'];

private _rect: BoundingRect;


constructor(
name: string,
geometries: GeoJSONRegion['geometries'],
Expand All @@ -77,17 +107,15 @@ export class GeoJSONRegion extends Region {

this.geometries = geometries;

if (!cp) {
const rect = this.getBoundingRect();
cp = [
rect.x + rect.width / 2,
rect.y + rect.height / 2
];
}
else {
cp = [cp[0], cp[1]];
}
this._center = cp;
this._center = cp && [cp[0], cp[1]];
}

calcCenter() {
const rect = this.getBoundingRect();
return [
rect.x + rect.width / 2,
rect.y + rect.height / 2
];
}

getBoundingRect(): BoundingRect {
Expand All @@ -102,20 +130,23 @@ export class GeoJSONRegion extends Region {
const min2 = [] as number[];
const max2 = [] as number[];
const geometries = this.geometries;
let i = 0;
for (; i < geometries.length; i++) {
// Only support polygon
if (geometries[i].type !== 'polygon') {
continue;
}
// Doesn't consider hole
const exterior = geometries[i].exterior;
bbox.fromPoints(exterior, min2, max2);

function updateBBoxFromPoints(points: number[][]) {
bbox.fromPoints(points, min2, max2);
vec2.min(min, min, min2);
vec2.max(max, max, max2);
}
each(geometries, geo => {
if (geo.type === 'polygon') {
// Doesn't consider hole
updateBBoxFromPoints(geo.exterior);
}
else {
each(geo.points, updateBBoxFromPoints);
}
});
// No data
if (i === 0) {
if (!geometries.length) {
min[0] = min[1] = max[0] = max[1] = 0;
}

Expand All @@ -131,12 +162,13 @@ export class GeoJSONRegion extends Region {
return false;
}
loopGeo: for (let i = 0, len = geometries.length; i < len; i++) {
const geo = geometries[i];
// Only support polygon.
if (geometries[i].type !== 'polygon') {
if (geo.type !== 'polygon') {
continue;
}
const exterior = geometries[i].exterior;
const interiors = geometries[i].interiors;
const exterior = geo.exterior;
const interiors = geo.interiors;
if (polygonContain.contain(exterior, coord[0], coord[1])) {
// Not in the region if point is in the hole.
for (let k = 0; k < (interiors ? interiors.length : 0); k++) {
Expand All @@ -163,19 +195,17 @@ export class GeoJSONRegion extends Region {
const transform = rect.calculateTransform(target);
const geometries = this.geometries;
for (let i = 0; i < geometries.length; i++) {
// Only support polygon.
if (geometries[i].type !== 'polygon') {
continue;
}
const exterior = geometries[i].exterior;
const interiors = geometries[i].interiors;
for (let p = 0; p < exterior.length; p++) {
vec2.applyTransform(exterior[p], exterior[p], transform);
const geo = geometries[i];
if (geo.type === 'polygon') {
transformPoints(geo.exterior, transform);
each(geo.interiors, interior => {
transformPoints(interior, transform);
});
}
for (let h = 0; h < (interiors ? interiors.length : 0); h++) {
for (let p = 0; p < interiors[h].length; p++) {
vec2.applyTransform(interiors[h][p], interiors[h][p], transform);
}
else {
each(geo.points, points => {
transformPoints(points, transform);
});
}
}
rect = this._rect;
Expand All @@ -194,23 +224,12 @@ export class GeoJSONRegion extends Region {
newRegion.transformTo = null; // Simply avoid to be called.
return newRegion;
}

getCenter() {
return this._center;
}

setCenter(center: number[]) {
this._center = center;
}

}

export class GeoSVGRegion extends Region {

readonly type = 'geoSVG';

private _center: number[];

// Can only be used to calculate, but not be modified.
// Becuase this el may not belongs to this view,
// but been displaying on some other view.
Expand All @@ -224,17 +243,7 @@ export class GeoSVGRegion extends Region {
this._elOnlyForCalculate = elOnlyForCalculate;
}

getCenter() {
let center = this._center;
if (!center) {
// In most cases there are no need to calculate this center.
// So calculate only when called.
center = this._center = this._calculateCenter();
}
return center;
}

private _calculateCenter(): number[] {
calcCenter(): number[] {
const el = this._elOnlyForCalculate;
const rect = el.getBoundingRect();
const center = [
Expand Down
Loading

0 comments on commit ae31f39

Please sign in to comment.