Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support label rotation #1247

Open
mramato opened this issue Oct 17, 2013 · 16 comments
Open

Support label rotation #1247

mramato opened this issue Oct 17, 2013 · 16 comments

Comments

@mramato
Copy link
Contributor

mramato commented Oct 17, 2013

As requested on the mailing list: https://groups.google.com/d/msg/cesium-dev/dnqBUH3Fx7M/iswm2vMz8DMJ

This might involve the same difficulties that prevent us from doing label scaling by distance, but I would have to check the label code to know for sure.

@Nirco96
Copy link

Nirco96 commented Dec 12, 2017

Have you gotten around to implemnting this feature yet?

@hpinkos
Copy link
Contributor

hpinkos commented Dec 12, 2017

@Nirco96 sorry, we haven't had a chance to add support for this yet. We'd be happy to review a pull request if anyone has time to implement this!

@hpinkos hpinkos changed the title Support label rotation... Support label rotation Jan 3, 2019
@lydonchandra
Copy link

lydonchandra commented Dec 17, 2019

@hpinkos could you please give us some pointers about where to start to implement this?

@OmarShehata
Copy link
Contributor

@lydonchandra I would look at the Label class where individual glyphs are drawn and the LabelCollection where it's all put together.

The labels use billboards under the hood for rotation, so it may be as simple as passing that through to some "parent" billboard that contains all the glyphs, or it may require handling the fact that all the glyphs may be separate billboards.

@mramato
Copy link
Contributor Author

mramato commented Dec 17, 2019

@lydonchandra thanks for being interested in taking a look at this!

all the glyphs may be separate billboards.

All glyphs are always separate billboards. It's been a while, but I'm pretty sure 99% of the work is simply modifying repositionAllGlyphs in LabelCollection.js to take a rotation into account. (and then the plumbing of exposing a rotation property on the Primitive/Entity APIs.)

To start, I would just recommend reviewing repositionAllGlyphs to understand what it's doing and then picking some angle (like 1/4 pi) and getting that to work. Once you have all labels drawing at that rotation, then it should be easy to thread it through up to the API.

@lydonchandra
Copy link

lydonchandra commented Dec 17, 2019

Thanks for the pointers @OmarShehata @mramato

Screenshot from
https://sandcastle.cesium.com/index.html?src=Callback%20Property.html&label=Beginner

image

As all glyphs are in separate billboards,

  1. Should each glyphCanvas position/rotation be adjusted separately based rotationAngle?
    In screenshot above, only rotation is adjusted (position is left untouched)
  2. Also the label's backgroundBillboard (shown when showBackground: true) can be rotated easily as it is just one billboard.

Is copying all glyphs to another billboard (that can be rotated easily) an acceptable solution?

@mramato
Copy link
Contributor Author

mramato commented Dec 17, 2019

@lydonchandra, unfortunately that's not an acceptable solution. The reason labels are rendered as individual glyphs and not as a single billboard are for runtime performance and memory usage. Obviously it complicates the implementation, but it's necessary technique. Otherwise creating/updating labels would be slower and consume a large amount of texture memory.

@jony89
Copy link
Contributor

jony89 commented Apr 2, 2020

@mramato This is extremely necessary feature fo e.g in order to change the angle of labels according to camera position. is this on the roadmap?

@OmarShehata
Copy link
Contributor

@jony89 can you describe your use case for that?

Do you mean rotating the label based on camera distance, around the axis between the camera and the label?

@jony89
Copy link
Contributor

jony89 commented Apr 2, 2020

That's one usecase. correct. to change the label position according to the heading of the camera. but in our app we would like to rotate the label for many other reasons. another use case is for measurement tools with labels next to the polyline that measures height.

@mramato
Copy link
Contributor Author

mramato commented Apr 2, 2020

@jony89 we would happily take a look at a pull request if you or your team is interested in contributing this feature, otherwise it is not on our near term roadmap. See our Contributing Guide to get started if you are interested.

@jony89
Copy link
Contributor

jony89 commented Apr 2, 2020

@mramato, on a side note, I am a contributor, yet the last PR I sent got really lil attention #8591 :\

@mramato
Copy link
Contributor Author

mramato commented Apr 2, 2020

@jony89 I reviewed that PR and asked you a question back on Feb 26th that you have yet to reply to: #8591 (comment)

@AnonymousSausage77
Copy link

Is there any news on this?

@BeyondBelief96
Copy link
Contributor

BeyondBelief96 commented May 24, 2024

If anyone is curious, I ended up needing something like this, (I wanted to show bearing and distance for each leg of a flight leg) and created some react components for this, and it works quite well for me. Of course, figuring out the angle to rotate your stuff by will be different than mine.

import React, { useEffect, useState } from 'react';
import { Cartesian3, Color, ScreenSpaceEventHandler } from 'cesium';
import { PolylineEntity } from './PolylineEntity';
import { LabelEntity } from './LabelEntity';
import { useCalcBearingAndDistanceMutation } from '../../../redux/api/vfr3d/navlog.api';
import { BearingAndDistanceResponseDto, Waypoint } from 'vfr3d-shared';
interface PolylineWithLabelProps {
  positions: Cartesian3[];
  color: Color;
  id: string;
  width: number;
  waypoints: Waypoint[];
  onLeftClick: (
    event: ScreenSpaceEventHandler.PositionedEvent,
    polylinePoints: Cartesian3[]
  ) => void;
}

export const BearingDistancePolyline: React.FC<PolylineWithLabelProps> = ({
  positions,
  color,
  id,
  width,
  onLeftClick,
  waypoints,
}) => {
  const [bearingAndDistance, setBearingAndDistance] = useState<BearingAndDistanceResponseDto>();
  const [bearingAndDistanceText, setBearingAndDistanceText] = useState<string>('');
  const [calcBearingAndDistance] = useCalcBearingAndDistanceMutation();
  const midpoint = Cartesian3.midpoint(positions[0], positions[1], new Cartesian3());

  useEffect(() => {
    const getBearingAndDistance = async () => {
      const bearingAndDistance = await calcBearingAndDistance({
        startPoint: waypoints[0],
        endPoint: waypoints[1],
      }).unwrap();

      setBearingAndDistance(bearingAndDistance);
      setBearingAndDistanceText(
        `TC: ${Math.round(bearingAndDistance.trueCourse)} - Distance: ${Math.round(bearingAndDistance?.distance)}`
      );
    };

    getBearingAndDistance();
  }, [calcBearingAndDistance, waypoints]);

  return (
    <>
      <PolylineEntity
        positions={positions}
        color={color}
        id={id}
        width={width}
        onLeftClick={onLeftClick}
      />
      {bearingAndDistance && (
        <LabelEntity
          position={midpoint}
          text={bearingAndDistanceText}
          rotation={bearingAndDistance.trueCourse - 90}
        />
      )}
    </>
  );
};
import {
  Cartesian3,
  Color,
  ConstantProperty,
  Entity,
  PolylineGraphics,
  ScreenSpaceEventHandler,
  ScreenSpaceEventType,
} from 'cesium';
import { useEffect, useRef } from 'react';
import { useCesium } from 'resium';

interface PolylineEntityProps {
  positions: Cartesian3[];
  color?: Color;
  width?: number;
  id: string;
  onLeftClick?: (
    position: ScreenSpaceEventHandler.PositionedEvent,
    polylinePoints: Cartesian3[]
  ) => void;
}

export const PolylineEntity: React.FC<PolylineEntityProps> = ({
  positions,
  color = Color.BLUE,
  width = 3,
  id,
  onLeftClick: onLeftClick,
}) => {
  const { viewer } = useCesium();
  const entityRef = useRef<Entity | null>(null);

  useEffect(() => {
    if (!viewer) return;
    const polylineGraphics = new PolylineGraphics({
      positions: new ConstantProperty(positions),
      material: color,
      width: new ConstantProperty(width),
    });

    const entity = viewer.entities.add({
      polyline: polylineGraphics,
      id,
    });

    entityRef.current = entity;

    const handler = new ScreenSpaceEventHandler(viewer.scene.canvas);

    if (onLeftClick) {
      handler.setInputAction((movement: ScreenSpaceEventHandler.PositionedEvent) => {
        const pickedObject = viewer.scene.pick(movement.position);
        if (pickedObject && pickedObject.id === entity) {
          onLeftClick(movement, positions);
        }
      }, ScreenSpaceEventType.LEFT_CLICK);
    }

    return () => {
      if (onLeftClick) {
        if (viewer && entity) {
          viewer.entities.remove(entity);
          handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
        }
      }
    };
  }, [viewer, positions, color, width, id, onLeftClick]);

  return null;
};
// LabelEntity.tsx
import {
  Entity,
  Color,
  ConstantProperty,
  NearFarScalar,
  Cartesian2,
  Property,
  Cartesian3,
  Plane,
  writeTextToCanvas,
  PlaneGraphics,
  ImageMaterialProperty,
  Transforms,
  HeadingPitchRoll,
  Math,
} from 'cesium';
import React, { useEffect, useRef } from 'react';
import { useCesium } from 'resium';

interface LabelEntityProps {
  position: Cartesian3;
  text: string;
  show?: boolean | Property;
  scale?: number | Property;
  color?: Color | Property;
  rotation?: number;
  id?: string;
}

export const LabelEntity: React.FC<LabelEntityProps> = ({
  position,
  text,
  show = true,
  scale = 1.0,
  color = Color.WHITE,
  rotation,
  id,
}) => {
  const { viewer } = useCesium();
  const entityRef = useRef<Entity | null>(null);

  useEffect(() => {
    if (!viewer) return;

    const image = writeTextToCanvas(text, {
      backgroundColor: Color.MAGENTA.withAlpha(0.1),
      padding: 2,
      fill: true,
      fillColor: Color.WHITE,
      stroke: true,
      strokeWidth: 1,
      strokeColor: Color.BLACK,
    });

    if (image) {
      const angle = rotation ? rotation : 0;
      const orientation = Transforms.headingPitchRollQuaternion(
        position,
        new HeadingPitchRoll(Math.toRadians(angle), 0, 0)
      );

      const offsetPosition = Cartesian3.add(
        position,
        Cartesian3.multiplyByScalar(new Cartesian3(-1, 0, 0), 1000, new Cartesian3()),
        new Cartesian3()
      );

      const entity = viewer.entities.add({
        position: offsetPosition,
        plane: new PlaneGraphics({
          plane: new ConstantProperty(new Plane(Cartesian3.UNIT_Z, 0.0)),
          dimensions: new ConstantProperty(new Cartesian2(image?.width * 50, image?.height * 50)),
          material: new ImageMaterialProperty({
            image: image,
          }),
          outline: false,
        }),
        orientation: orientation,
        id,
      });

      entityRef.current = entity;

      return () => {
        if (viewer && entity) {
          viewer.entities.remove(entity);
        }
      };
    }
  }, [viewer, position, text, show, scale, color, rotation, id]);

  return null;
};

@ibreathebsb
Copy link

ibreathebsb commented Dec 10, 2024

Wow it's been ten years,this feature is still not implemented

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests