VJMAP logoVJMAP
CAD · WebGIS
中

Query Example (Auto-Label Positions by Survey Point ID)

Updated 2026-03-07~ 14 min read
.md

No manual labeling required—automatically label positions on CAD drawings in the web client based on survey point IDs

Requirements Analysis

The CAD drawing looks like this:

image-20220913205333681
image-20220913205333681

The backend database only has survey point IDs and values, as in the list below:

Survey Point ID Type Value

DZ129-2 Type 1 Value 1

DZ128-2 Type 1 Value 2

DZ127-2 Type 1 Value 3

J28 Type 2 Value 5

J26 Type 2 Value 6

J27 Type 2 Value 7

J29 Type 2 Value 8

.......

How can we display these survey point values at the corresponding positions on the CAD drawing? See below:

Since the backend database has no survey point coordinates, the traditional approach is to manually label each survey point on the map, save the positions to the database, and then retrieve both positions and survey data for display. The drawback is obvious: with many survey points, manual labeling is time-consuming and labor-intensive.

Is there a better way to compute survey point positions from the drawing with one click?

Implementation

Pattern Analysis

To find the geographic location for each survey point ID, we need to know the pattern or rules for each type.

For the two survey point types in the example:

Survey Type 1

image-20220913210838860
image-20220913210838860

Pattern: Each survey point has a filled triangular symbol beside it

Survey Type 2

image-20220913210939752
image-20220913210939752

Pattern: Each survey point has a filled red circular symbol beside it

Find Corresponding Graphic Symbols by Pattern

There are several ways to search, such as image recognition or geometry analysis. Image recognition is sensitive to noise and slower. Geometry analysis is faster and more accurate. We use geometry analysis here.

Steps:

(1) Traverse all entity types in the drawing

(2) Determine matches by entity type and extent size

Search for Survey Point IDs Near Graphic Symbols and Build Associations

Steps:

(1) Search for text within a certain range of the symbol (e.g., a multiple of the symbol extent) and pick the nearest text

(2) Filter out non–survey point IDs by content rules (e.g., in this drawing survey IDs start with DZ or J and have limited length)

(3) Among all valid survey point IDs, pick the one closest to the symbol as the final survey point ID

(4) Associate the survey point ID with the symbol coordinates.

Key Code Implementation

 const queryMapText = async () => {
            let query = await svc.conditionQueryFeature({
                // Query all text (single-line, multiline, block annotation, attribute text). See "Server-side Condition Query and Expression Query - Supported CAD Entity Types" for type numbers
                condition: `(name='12' or name='13' or name='26' or name='27') and LENGTH(s4) >= 2 and LENGTH(s4) <=12 and ( s4 like 'J%'  or s4 like 'DZ%')`,
                fields: "",
                limit: 100000 // Query all
            })
            if (query.error) {
                message.error(query.error)
            } else {
                query.result.forEach(rst => {
                    rst.position = vjmap.GeoPoint.fromString(rst.location); // Get coordinates
                })
                return  query.result;
            }
        }
        // Get text info from the drawing
        let mapAllText = await queryMapText();
        console.log(mapAllText);

        // Find all hatches
        const queryHatch = async () => {
            let query = await svc.conditionQueryFeature({
                condition: `(name='11') `, // Type hatch
                fields: "",
                includegeom: true,
                limit: 100000 // Query all
            })
            if (query.error) {
                message.error(query.error)
            } else {
                query.result.forEach(rst => {
                    rst.geoBounds = vjmap.GeoBounds.fromString(rst.bounds); // Get extent
                    let w = rst.geoBounds.width();
                    let h = rst.geoBounds.height();
                    // If radius is 2, it's a circle, J_ type
                    if (vjmap.isZero(w - 2, 0.1) && vjmap.isZero(h - 2, 0.1))
                    {
                        rst.type = "circle_20";
                    } else {
                        if (rst.geom.geometries[0].type == "Polygon" && rst.geom.geometries[0].coordinates[0].length == 5) {
                            // If triangle hatch
                            if (vjmap.isZero(w - 2.5, 1) && vjmap.isZero(h - 2.5, 1))
                            {
                                rst.type = "triangle";
                            }

                        }
                    }

                })

                return  query.result;
            }
        }
        // Get hatch info from the drawing
        let mapAllCircleHatch = await queryHatch();
        console.log(mapAllCircleHatch);

        // Circles are paired with text starting with J
        let dataCircleTextG = mapAllCircleHatch.filter(rst => rst.type == "circle_28")
        dataCircleTextG.forEach(circle => {
            // Find the nearest text starting with J
            let texts = mapAllText.filter(t => t.text.substr(0, 1) == "J")
            let findTexts = texts.filter(t => t.position.distanceTo(circle.geoBounds.center()) <= 2.8 * 3) // Find matching text
            // Pick the nearest
            if (findTexts.length > 0) {
                findTexts = findTexts.sort((a, b) => a.position.distanceTo(circle.geoBounds.center()) - b.position.distanceTo(circle.geoBounds.center()) )
                drawDatas.push({
                    point: circle.geoBounds.center(),
                    name: findTexts[0].text,
                    color: "#0f0"
                })
            }

        })

        // Triangles are paired with text starting with DZ
        let dataTriangleTextDz = mapAllCircleHatch.filter(rst => rst.type == "triangle")
        dataTriangleTextDz.forEach(tri => {
            // Find the nearest text starting with DZ
            let texts = mapAllText.filter(t => t.text.substr(0, 2) == "DZ")
            let findTexts = texts.filter(t => t.position.distanceTo(tri.geoBounds.center()) <= 2.8 * 3) // Find matching text
            // Pick the nearest
            if (findTexts.length > 0) {
                findTexts = findTexts.sort((a, b) => a.position.distanceTo(tri.geoBounds.center()) - b.position.distanceTo(tri.geoBounds.center()) )
                drawDatas.push({
                    point: tri.geoBounds.center(),
                    name: findTexts[0].text,
                    color: "#f0f"
                })
            }
        })

        console.log(drawDatas)
        // Display data on the map
        let symbols;
        const showDataInMap = (datas)=> {
            let geoDatas = []
            for(let i = 0; i < datas.length; i++) {
                const pt = datas[i].point;
                const data = {
                    point: map.toLngLat(pt),
                    properties: {
                        name:  datas[i].name,
                        textColor: datas[i].color
                    }
                }
                geoDatas.push(data);
            }
            if (symbols) {
                symbols.remove();// Remove existing first
            }
            symbols = new vjmap.Symbol({
                data: geoDatas,
                iconImage: "stretchTextBackImg",
                iconAnchor: "bottom",
                iconOpacity: 0.5,
                iconOffset: [-2, -10],
                textTranslate: [-2, -6],
                textAnchor: "bottom",
                textField: ['get', 'name'],
                textFont: ['Arial Unicode MS Regular'],
                textSize: 14,
                textColor: ['get', 'textColor'],
                iconTextFit: "both",
                iconAllowOverlap: true,
                textAllowOverlap: true
            });
            symbols.addTo(map);
        }
        showDataInMap(drawDatas);

Final Result

image-20220913212244279
image-20220913212244279

The example code above is open source. For data privacy, the demo uses sample maps from the VJMap website; the approach is the same. Visit https://vjmap.com/demo/#/demo/map/service/22findtextbyrules to see the effect and code.

Was this page helpful?