Generar mapas realistas

25

Dibujé este mapa de las regiones de una palabra imaginaria en unos minutos en MS Paint:

mi mapa

Creo que ser capaz de generar mapas como este mediante programación sería realmente genial.

Reto

Escriba un programa que tome enteros positivos Wy H, y un conjunto no vacío de enteros positivos S.

Genere una imagen estándar de color verdadero que tenga Wpíxeles de ancho por Hpíxeles de alto.

Para cada número entero ien S, dibujar una región plana en la imagen cuya área en píxeles es proporcional a i, el uso de un color diferente de cualquier regiones vecinas. Específicamente, el número de píxeles en la región debe W * H * i / sum(S)redondearse hacia arriba o hacia abajo para garantizar que cada píxel de la imagen pertenezca a una región .

Una región plana es un conjunto de píxeles con la propiedad de que cualquier píxel en la región se puede alcanzar desde cualquier otro permaneciendo dentro de la región y solo moviéndose ortogonalmente (y no diagonalmente). Mi mapa de arriba tiene 10 regiones planas.

Todos los píxeles en una región plana deben ser del mismo color, que debe ser diferente al color de cualquier región vecina. Las regiones pueden ser del mismo color si no son vecinas.

De lo contrario, no hay limitaciones en cuanto a la forma, posición o color de sus regiones. Este es un concurso de popularidad. El objetivo es crear un programa que haga mapas realistas de mundos imaginarios, físicos o políticos, con cualquier geografía, a cualquier escala.

Naturalmente, muestre sus mejores imágenes de salida, no solo su código.

Detalles

  • Tome la entrada del archivo, línea de comando, stdin o similar. Guarde la imagen en cualquier formato estándar o muéstrela a la pantalla.
  • Su programa debe ser determinista para entradas idénticas. Es decir, la imagen de salida debe ser siempre el mismo para algunos en particular H, Wy S. (Tenga en cuenta que Ses un conjunto , no una lista, por lo que su orden no importa). De lo contrario, puede emplear la aleatoriedad donde lo desee, aunque no es obligatorio (pero lo recomiendo).
  • La geografía de la imagen de salida no necesita "escalar" para diferentes valores de Wo H(aunque podría). Puede ser completamente diferente.
  • Puede asignar colores al azar, sin tener en cuenta la regla de color vecino, siempre que haya al menos 32 posibilidades de color aleatorio, ya que sería poco probable que dos vecinos se coloreen de la misma manera.
  • Las regiones se detienen en los límites de la imagen. No hay vuelta alrededor .
  • Las regiones pueden contener cero píxeles (y, por lo tanto, no existir), como sería el caso cuando hay más regiones que píxeles.

Entrada de ejemplo

Un envío válido podría haber generado mi mapa anterior con los parámetros:

W = 380
H = 260
S = {233, 420, 1300, 3511, 4772, 5089, 9507, 22107, 25117, 26744}

Estos Svalores son exactamente iguales al número de píxeles en cada región, pero ese no tiene por qué ser el caso. Recuerde que Ses un conjunto, por lo que no siempre se ordena necesariamente.

Pasatiempos de Calvin
fuente

Respuestas:

15

Estoy de acuerdo con los demás. Este fue un desafío sorprendentemente difícil. En parte debido al requisito de tener píxeles adyacentes conectados del mismo tipo de región, pero también debido al desafío estético de hacer que las regiones se vean como un mapa de países.

Aquí está mi intento ... es terriblemente ineficiente pero parece producir resultados razonables. Continuando con la tendencia de usar entradas comunes para propósitos de comparación:

Parámetros: 3802602333203 1300 3511 4772 5089 9507 22107 25117 26744

ingrese la descripción de la imagen aquí

Parámetros: 380260 8 5 6 7 8 4 5 6 7 9 4 6 9 5 8 7 5

ingrese la descripción de la imagen aquí

Edad Oscura de Camelot 213307 1 1 1

ingrese la descripción de la imagen aquí

Mi ejemplo más grande: (640 480 6 1 7 2 9 3 4 5 6 1 9 8 7 44 3 1 9 4 5 6 7 2 3 4 9 3 4 5 9 8 7 5 6 1 2 1 2 1 2 6 7 8 9 63 3)

ingrese la descripción de la imagen aquí

Un ejemplo con más países: 640480 6 1 7 2 9 3 4 5 6 1 9 8 7 44 3 1 9 4 5 6 7 2 3 4 9 3 4 5 9 8 7 5 6 1 2 1 2 1 2 6 7 8 9 63 5 33 11 88 2 7 9 5 6 2 5 7

package GenerateRealisticMaps;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.imageio.ImageIO;

public class GenerateRealisticMaps
{
    private static final Random rand = new Random(3);
    private static final Color[] paletteizedColours = new Color[100];

    // create colour palette
    static
    {
        paletteizedColours[0] = new Color(0xFF000000);
        for (int i = 1; i < paletteizedColours.length; i++)
        {
            paletteizedColours[i] = Color.getHSBColor(rand.nextFloat(), rand.nextFloat(), 0.5f + rand.nextFloat() * 0.4f);
        }
    }

    /**
     * Represents a pixel that is the boundary of a region
     * @author default
     *
     */
    public static class BoundaryPixel
    {
        public BoundaryPixel(int x, int y, int otherRegionId)
        {
            super();
            this.x = x;
            this.y = y;
            this.otherRegionId = otherRegionId;
        }

        int x;
        int y;
        int otherRegionId;
    }

    /**
     * Group of adjacent pixels that represent a region (i.e. a country in the map)
     * @author default
     *
     */
    public static class Region
    {
        static private int masterId = 0;

        Region(int desiredSize)
        {
            this.desiredSize = desiredSize;
            id = ++masterId;
        }

        int desiredSize;
        int size = 0;
        int id;
        List<BoundaryPixel> boundary = new ArrayList<GenerateRealisticMaps.BoundaryPixel>();

    }

    /**
     * Container of regions
     * @author default
     *
     */
    public static class Regions
    {
        List<Region> regionList = new ArrayList<GenerateRealisticMaps.Region>();
        Map<Integer, Region> regionMap = new HashMap<Integer, GenerateRealisticMaps.Region>();
    }

    public static void main(String[] args) throws IOException
    {
        int width = Integer.parseInt(args[0]);
        int height = Integer.parseInt(args[1]);
        int[] s = new int[args.length - 2];

        // read in the region weights
        int sum = 0;
        for (int i = 0; i < args.length - 2; i++)
        {
            sum += s[i] = Integer.parseInt(args[i + 2]);
        }

        int totalPixels = width * height;

        double multiplier = ((double) totalPixels) / sum;

        // convert region weights to pixel counts
        int runningCount = 0;
        for (int i = 0; i < s.length - 1; i++)
        {
            runningCount += s[i] = (int) (multiplier * s[i]);
        }
        s[s.length - 1] = totalPixels - runningCount;

        Regions regions = new Regions();
        int[][] map = new int[width][height];

        // initialise region starting pixels
        for (int v : s)
        {
            Region region = new Region(v);
            regions.regionList.add(region);
            regions.regionMap.put(region.id, region);

            int x;
            int y;
            do
            {
                x = rand.nextInt(width);
                y = rand.nextInt(height);
            } while (map[x][y] != 0);

            map[x][y] = region.id;
            region.size++;

        }

        // initialise a "height" map that provides cost to claim a unclaimed region. This allows for more natural shaped countries
        int[][] heightMap = new int[width][height];
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                heightMap[i][j] = rand.nextInt(50);
            }
        }

        boolean equal = false;

        // main loop
        do
        {
            growRegions(map, heightMap, width, height, regions);

            // determine whether regions have reached their desired size
            equal = true;
            for (Region region : regions.regionList)
            {
                equal = equal && region.size == region.desiredSize;
            }

            if (equal)
            {
                HashMap<Integer, Set<Integer>> commonIsolatedRegions = new HashMap<Integer, Set<Integer>>();
                int isolatedRegionId = 0;
                int[][] isolatedRegions = new int[width][height];
                List<Integer> isolatedRegionSize = new ArrayList<Integer>();
                isolatedRegionSize.add(-1); // add dummy entry at index 0 since region ids start at 1

                // go though each pixel and attempt to identify an isolated region from that point if it as not
                // yet been identified... i.e. an enclosed area.
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        if (isolatedRegions[i][j] == 0)
                        {
                            isolatedRegionId++;

                            Point point = new Point(i, j);
                            int size = identifyEnclosedArea(map, isolatedRegions, width, height, point, isolatedRegionId);

                            // add this isolated region id to the group of isolated regions associated with the region at this pixel
                            Set<Integer> isolatedRegionSet = commonIsolatedRegions.get(map[i][j]);
                            if (isolatedRegionSet == null)
                            {
                                isolatedRegionSet = new HashSet<Integer>();
                                commonIsolatedRegions.put(map[i][j], isolatedRegionSet);
                            }
                            isolatedRegionSet.add(isolatedRegionId);
                            isolatedRegionSize.add(size);
                        }
                    }
                }

                // only keep the largest isolated region in each group. Mark the other members in the group areas as unclaimed.
                for (Region region : regions.regionList)
                {
                    Set<Integer> isolatedRegionSet = commonIsolatedRegions.get(region.id);

                    // find the largest isolatedRegion mapped to this region
                    int largestIsolatedRegionId = -1;
                    int largestIsolatedRegionSize = -1;
                    for (Integer isolatedRegionIdentifier : isolatedRegionSet)
                    {
                        if (isolatedRegionSize.get(isolatedRegionIdentifier) > largestIsolatedRegionSize)
                        {
                            largestIsolatedRegionSize = isolatedRegionSize.get(isolatedRegionIdentifier);
                            largestIsolatedRegionId = isolatedRegionIdentifier;
                        }
                    }
                    // remove the largest isolated region (i.e. retain those pixels)
                    isolatedRegionSet.remove(largestIsolatedRegionId);

                    if (isolatedRegionSet.size() > 0)
                    {
                        equal = false;

                        // for all remaining isolated regions mapped to this region, convert to unclaimed areas.
                        for (Integer isolatedRegionIdentifier : isolatedRegionSet)
                        {
                            for (int i = 0; i < width; i++)
                            {
                                for (int j = 0; j < height; j++)
                                {
                                    if (isolatedRegions[i][j] == isolatedRegionIdentifier)
                                        map[i][j] = 0;
                                }
                            }
                        }
                    }
                }
            }

        } while (!equal);

        saveOutputImage("out.final.png", map);
    }

    /**
     * Renders and saves the output image
     * 
     * @param filename
     * @param map
     * @throws IOException
     */
    public static void saveOutputImage(String filename, int[][] map) throws IOException
    {

        final int scale = 1;
        final int width = map.length;
        final int height = map[0].length;
        BufferedImage image = new BufferedImage(width * scale, height * scale, BufferedImage.TYPE_INT_RGB);

        Graphics2D g = (Graphics2D) image.getGraphics();

        for (int j = 0; j < height; j++)
        {
            for (int i = 0; i < width; i++)
            {
                g.setColor(paletteizedColours[map[i][j]]);
                g.fillRect(i * scale, j * scale, scale, scale);
            }
        }

        ImageIO.write(image, "png", new File(filename));
    }

    /**
     * Grows the regions of the world. Firstly by unclaimed cells and then by distributing cells amongst the regions.
     * 
     * @param map
     *            cell to region map
     * @param heightMap
     *            the "height" cost of unclaimed cells. Used to give more natural shapes.
     * @param width
     * @param height
     * @param regions
     */
    public static void growRegions(int[][] map, int[][] heightMap, int width, int height, Regions regions)
    {
        // reset region sizes
        for (Region region : regions.regionList)
        {
            region.size = 0;
            region.boundary.clear();
        }

        // populate corners with adjacent pixel region id... these pixels cannot ever be "grown" into.
        map[0][0] = map[1][0];
        map[width - 1][0] = map[width - 1][5];
        map[width - 1][height - 1] = map[width - 2][height - 1];
        map[0][height - 1] = map[1][height - 1];

        int i, x, y, dx = 0, dy = 0, currHeight, currentId = -1, pixelRegionId;
        Region currRegion = null;
        ;

        // calculate initial region sizes
        for (y = 0; y < height; y++)
        {
            for (x = 0; x < width; x++)
            {
                if (map[x][y] > 0)
                    regions.regionMap.get(map[x][y]).size++;
            }
        }

        // expand regions into surrounding unclaimed pixels.
        // construct a list of region boundary pixels in the process.
        for (y = 1; y < height - 1; y++)
        {
            for (x = 1; x < width - 1; x++)
            {
                int cellId = map[x][y];
                if (cellId > 0)
                {
                    if (cellId != currentId)
                    {

                        currRegion = regions.regionMap.get(map[x][y]);
                        currentId = currRegion.id;
                    }

                    currHeight = heightMap[x][y]++;

                    for (i = 0; i < 4; i++)
                    {
                        switch (i)
                        {
                        case 0:
                            dx = x - 1;
                            dy = y;
                            break;
                        case 1:
                            dx = x + 1;
                            dy = y;
                            break;
                        case 2:
                            dx = x;
                            dy = y - 1;
                            break;
                        case 3:
                            dx = x;
                            dy = y + 1;
                            break;
                        }
                        pixelRegionId = map[dx][dy];
                        switch (pixelRegionId)
                        {
                        // unclaimed cell...
                        case 0:
                            if (heightMap[dx][dy] < currHeight)
                            {
                                map[dx][dy] = currRegion.id;
                                currRegion.size++;
                            }
                            break;
                        // claimed cell...
                        default:
                            if (pixelRegionId != currRegion.id)
                            {
                                currRegion.boundary.add(new BoundaryPixel(dx, dy, pixelRegionId));
                            }
                            break;
                        }
                    }
                }
            }
        }

        HashMap<Integer, List<BoundaryPixel>> neighbourBorders = new HashMap<Integer, List<BoundaryPixel>>();

        // for all regions...
        for (Region region : regions.regionList)
        {
            // that are less than the desired size...
            if (region.size < region.desiredSize)
            {
                neighbourBorders.clear();

                // identify the boundary segment per neighbour of the region
                for (BoundaryPixel boundaryPixel : region.boundary)
                {
                    List<BoundaryPixel> neighbourBorderSegment = neighbourBorders.get(boundaryPixel.otherRegionId);
                    if (neighbourBorderSegment == null)
                    {
                        neighbourBorderSegment = new ArrayList<GenerateRealisticMaps.BoundaryPixel>();
                        neighbourBorders.put(boundaryPixel.otherRegionId, neighbourBorderSegment);
                    }
                    neighbourBorderSegment.add(boundaryPixel);
                }

                out:
                // for each neighbour...
                for (int id : neighbourBorders.keySet())
                {
                    Region neighbourRegion = regions.regionMap.get(id);
                    int surplusPixelCount = neighbourRegion.size - neighbourRegion.desiredSize;
                    // that has surplus pixels...
                    if (surplusPixelCount > 0)
                    {
                        // and convert the border segment pixels to the current region...
                        List<BoundaryPixel> neighbourBorderSegment = neighbourBorders.get(id);
                        int index = 0;
                        while (surplusPixelCount-- > 0 && index < neighbourBorderSegment.size())
                        {
                            BoundaryPixel boundaryPixel = neighbourBorderSegment.get(index++);
                            map[boundaryPixel.x][boundaryPixel.y] = region.id;
                            region.size++;
                            regions.regionMap.get(boundaryPixel.otherRegionId).size--;
                            // until we reach the desired size...
                            if (region.size == region.desiredSize)
                                break out;
                        }
                    }
                }
            }

            // if region contains more pixels than desired...
            else if (region.size > region.desiredSize)
            {
                // and the region has neighbours
                if (region.boundary.size() > 0)
                {
                    // choose a neighbour to off load extra pixels to
                    Region neighbour = regions.regionMap.get(region.boundary.remove(rand.nextInt(region.boundary.size())).otherRegionId);

                    ArrayList<BoundaryPixel> adjustedBoundary = new ArrayList<>();
                    // iterate over the boundary neighbour's boundary pixels...
                    for (BoundaryPixel boundaryPixel : neighbour.boundary)
                    {
                        // and then for those pixels which are of the current region, convert to the neighbour region
                        if (boundaryPixel.otherRegionId == region.id)
                        {
                            map[boundaryPixel.x][boundaryPixel.y] = neighbour.id;
                            neighbour.size++;
                            region.size--;
                            // stop when we reach the region's desired size.
                            if (region.size == region.desiredSize)
                                break;
                        }
                        else
                        {
                            adjustedBoundary.add(boundaryPixel);
                        }
                    }
                    neighbour.boundary = adjustedBoundary;
                }
            }
        }

    }

    /**
     * identifies the area, starting at the given point, in which adjacent pixels are of the same region id.
     * 
     * @param map
     * @param isolatedRegionMap
     *            cells identifying which area that the corresponding map cell belongs
     * @param width
     * @param height
     * @param point
     *            the starting point of the area to be identified
     * @param isolatedRegionId
     *            the id of the region to assign cells with
     * @return the size of the identified area
     */
    private static int identifyEnclosedArea(int[][] map, int[][] isolatedRegionMap, int width, int height, Point point, final int isolatedRegionId)
    {
        ArrayList<Point> stack = new ArrayList<Point>();
        final int EXPECTED_REGION_ID = map[point.x][point.y];
        stack.add(point);
        int size = 0;

        while (stack.size() > 0)
        {
            Point p = stack.remove(stack.size() - 1);
            int x = p.x;
            int y = p.y;
            if (y < 0 || y > height - 1 || x < 0 || x > width - 1 || isolatedRegionMap[x][y] > 0)
                continue;
            int val = map[x][y];
            if (val == EXPECTED_REGION_ID)
            {
                isolatedRegionMap[x][y] = isolatedRegionId;
                size++;
                stack.add(new Point(x + 1, y));
                stack.add(new Point(x - 1, y));
                stack.add(new Point(x, y + 1));
                stack.add(new Point(x, y - 1));
            }
        }

        return size;
    }

}

Explicación (de los comentarios)

El algoritmo es bastante simple: en primer lugar, inicialice el mapa con pesos aleatorios, elija píxeles semilla aleatorios para cada una de las regiones del país. En segundo lugar, "haga crecer" cada región intentando reclamar píxeles adyacentes no reclamados. Esto ocurre cuando el peso del píxel actual excede el peso no reclamado.

Cada píxel en una región aumenta su peso en cada ciclo de crecimiento. Además, si una región tiene vecinos, entonces si la región actual que se está considerando tiene menos píxeles de lo deseado, robará píxeles de su vecino si el vecino tiene más píxeles de los deseados. Si la región actual tiene más píxeles que su vecino, entonces elige aleatoriamente un vecino y luego le da todos los píxeles sobrantes a ese vecino. Cuando todas las regiones tienen el tamaño correcto, se produce la tercera fase para identificar y convertir las regiones que se han dividido y que ya no son continuas.

Solo se mantiene la división más grande de la región y las otras divisiones se convierten en píxeles no reclamados y la segunda fase comienza de nuevo. Esto se repite hasta que todos los píxeles de una región sean adyacentes y todas las regiones tengan el tamaño correcto.

Moogie
fuente
Eso es bueno ! ¿Podría explicar un poco cómo funciona su algoritmo?
Arnaud
1
¡Excelente! Creo que solo los colores podrían ser más "Terrosos" (el tercero se parece más a la Edad Púrpura Oscura de Camelot: P). Creo que olvidaste la imagen para tu último ejemplo.
Aficiones de Calvin
@ Calvin'sHobbies tiene que amar la selección aleatoria de colores ... parece que mi computadora tiene una inclinación por el púrpura: P. Vaya, olvidé el tercer ejemplo ... generará y actualizará.
Moogie
12

Este desafío es sorprendentemente difícil. Escribí un generador de mapas en Python usando Pygame. El programa aumenta el área de color en el espacio libre y da como resultado una imagen que podría parecer un mapa (si entrecierra los ojos).

Mi algoritmo no siempre completa los países, ya que el área restante puede no tener suficiente espacio, pero pensé que resultó en un efecto interesante, y no pasaré más tiempo en ello. Los parches azules que quedan pueden considerarse grandes lagos, y las características azules moteadas entre los países son los ríos que marcan la frontera (¡es una característica, no un error!).

Para comparar con el Super Chafouin, utilicé sus ejemplos de parámetros.

Parámetros: 3802602333203 1300 3511 4772 5089 9507 22107 25117 26744

Prueba estándar

Parámetros: 380260 8 5 6 7 8 4 5 6 7 9 4 6 9 5 8 7 5

Ejemplo 2

Edad Oscura de Camelot (213307 1 1 1)

Ejemplo 3

Mi ejemplo más grande: (640 480 6 1 7 2 9 3 4 5 6 1 9 8 7 44 3 1 9 4 5 6 7 2 3 4 9 3 4 5 9 8 7 5 6 1 2 1 2 1 2 6 7 8 9 63 3)

¿Mi ejemplo más grande con Europa del Este?

Este ejemplo se parece un poco a Europa del Este?

Un ejemplo con más países: 640480 6 1 7 2 9 3 4 5 6 1 9 8 7 44 3 1 9 4 5 6 7 2 3 4 9 3 4 5 9 8 7 5 6 1 2 1 2 1 2 6 7 8 9 63 5 33 11 88 2 7 9 5 6 2 5 7

Más países con colores suaves.

Cambié el generador de color con este ejemplo para colors = [(80+ri(100), 80+ri(100), 80+ri(100)) for c in counts]obtener un rango más suave (y similar a un mapa).

Código de Python:

from pygame.locals import *
import pygame, sys, random

BACK = (0,0,200)
ORTH = [(-1,0), (1,0), (0,-1), (0,1)]
PI = 3.141592

random.seed(9999)
def ri(n):
    return int(random.random() * n)

args = [int(v) for v in sys.argv[1:]]
W, H = args[:2]
shares = sorted(args[2:])
ratio = float(W*H) / sum(shares)
counts = [int(s*ratio) for s in shares]
for i in range(W*H - sum(counts)):
    counts[i] += 1

colors = [(2+ri(250), 2+ri(250), 2+ri(250)) for c in counts]
countries = range(len(counts))
random.shuffle(countries)

border = ( set((x,y) for x in (0,W-1) for y in range(H)) |
            set((x,y) for x in range(W) for y in (0,H-1)) )

screen = pygame.display.set_mode((W,H))
screen.fill(BACK)
pix = screen.set_at
def look(p):
    if 0 <= p[0] < W and 0 <= p[1] < H:
        return screen.get_at(p)
    else:
        return None

clock = pygame.time.Clock()

while True:
    dt = clock.tick(300)
    pygame.display.flip()

    if countries:
        country = countries.pop()
        color = colors[country]
        if not countries:
            color = (20,20,200)  # last fill color to be water
        count = counts[country]
        frontier = set()
        plotted = 0
        loc = border.pop()
        while plotted < count:
            pix(loc, color)
            if plotted % 50 == 0:
                pygame.display.flip()
            plotted += 1
            direc = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
            for dloc in direc:
                if look(dloc) == BACK:
                    frontier.add(dloc)
            border |= frontier
            if frontier:
                loc = frontier.pop()
                border.discard(loc)
            else:
                print 'Country %s cover %u of %u' % (
                    shares[country], plotted, count)
                break
        if not countries:
            fn = 'mapper%u.png' % ri(1000)
            pygame.image.save(screen, fn)

    for event in pygame.event.get():
        if event.type == QUIT: sys.exit(0)
        if not hasattr(event, 'key'): continue
        if event.key == K_ESCAPE: sys.exit(0)
Caballero Lógico
fuente
No estoy seguro de que respete la regla "any pixel in the region can be reached from any other by staying within the region and only moving orthogonally". ¿Veo píxeles aislados?
Arnaud
El algoritmo cumple con la regla de continuidad que ha citado pero falla en otras áreas. Esos píxeles aislados son "agujeros" en el país donde se ve el fondo. Debido a este efecto y otros, varios países en cada ejecución no tendrán todos sus píxeles generados. Algunos países pierden la mayoría de sus píxeles. No cumple con todas las reglas especificadas, pero pensé que era un resultado interesante. El algoritmo necesitaría un trabajo significativo para generar un mapa perfecto.
Logic Knight
Técnicamente está en contra de las reglas, pero sigue siendo bastante bueno. Intenté algo como esto después de hacer la pregunta y tengo problemas similares. ¡Es más complicado de lo que pensaba!
Hobbies de Calvin
8

¡Seamos flojos y adaptemos mi respuesta a esta pregunta !

  1. El algoritmo calcula un "camino de serpiente" a partir de la esquina superior izquierda que llena todo el rectángulo. La serpiente solo puede ir arriba, abajo, izquierda, derecha.

  2. Se sigue el camino de la serpiente y se llena con el primer color, luego el segundo color, etc. teniendo en cuenta los porcentajes de color.

  3. Este algoritmo produce muchas líneas rectas; para mejorarlo, los detecto y los reemplazo con "ondas" que mantienen la misma cantidad de píxeles.

Parámetros: 3802602333203 1300 3511 4772 5089 9507 22107 25117 26744

ingrese la descripción de la imagen aquí

Parámetros: 380260 8 5 6 7 8 4 5 6 7 9 4 6 9 5 8 7 5

ingrese la descripción de la imagen aquí

Edad Oscura de Camelot (213307 1 1 1)

ingrese la descripción de la imagen aquí

El código:

package map;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;


public class GenMap2 {

    private enum State { NO, YES, SHIFT };
    public final static int TOP = 1, BOTTOM = 2, LEFT = 4, RIGHT = 8;
    enum Action { ADD_LINE_TOP, ADD_LINE_LEFT, DOUBLE_SIZE, CREATE};

    public static void main(String[] args) throws IOException {

        int w = Integer.parseInt(args[0]), h = Integer.parseInt(args[1]);
        List<Integer> areas = new ArrayList<Integer>();
        int total = 0;
        for (int i = 2; i < args.length; i++) {
            int area = Integer.parseInt(args[i]);
            areas.add(area);
            total += area;
        }
        Collections.sort(areas);
        Collections.reverse(areas);
        int [][] tab = build(w, h);

        BufferedImage dest = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        int [] black = {0, 0, 0};
        for (int j = 0; j < dest.getHeight(); j++) {
            for (int i = 0; i < dest.getWidth(); i++) {
                dest.getRaster().setPixel(i, j, black);
            }
        }

        int x = 0, y = -1;
        int go = BOTTOM, previous = BOTTOM;

        List<Color> colors = new ArrayList<Color>();
        Random rand = new Random(0); // prog must be deterministic
        while (colors.size() < areas.size()) {
            Color c = new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256));
            boolean ok = true;
            for (Color existing : colors) {
                if (existing.equals(c)) {
                    ok = false;
                    break;
                }
            }
            if (ok) {
                colors.add(c);
            }
        }

        int [][] map = new int[w][h];
        int cpt = 0;
        while (true) {
            if (go == BOTTOM) y++;
            if (go == TOP) y--;
            if (go == LEFT) x--;
            if (go == RIGHT) x++;

            int tmp = (int)(((long)cpt) * total / (w * h));
            int i = 0;
            for (i = 0; i < areas.size(); i++) {
                int area = areas.get(i);
                if (tmp < area) {
                    break;
                }
                tmp -= area;
            }

            map[x][y] = i;

            previous = go;

            go = -1;
            if ((tab[x][y] & TOP) != 0 && previous != BOTTOM) go = TOP;
            if ((tab[x][y] & BOTTOM) != 0 && previous != TOP) go = BOTTOM;
            if ((tab[x][y] & LEFT) != 0 && previous != RIGHT) go = LEFT;
            if ((tab[x][y] & RIGHT) != 0 && previous != LEFT) go = RIGHT;
            if (go == -1) break;
            cpt++;
        }

        String [] src0 = srcPattern(16);
        String [] repl0 = destPattern(16);
        while (findPattern(map, src0, Arrays.asList(repl0, flip(repl0)))){}
        while (findPattern(map, rotate(src0), Arrays.asList(rotate(repl0), rotate(flip(repl0))))){}
        String [] src1 = srcPattern(8);
        String [] repl1 = destPattern(8);
        while (findPattern(map, src1, Arrays.asList(repl1, flip(repl1)))){}
        while (findPattern(map, rotate(src1), Arrays.asList(rotate(repl1), rotate(flip(repl1))))){}
        String [] src2 = srcPattern(4);
        String [] repl2 = destPattern(4);
        while (findPattern(map, src2, Arrays.asList(repl2, flip(repl2)))){}
        while (findPattern(map, rotate(src2), Arrays.asList(rotate(repl2), rotate(flip(repl2))))){}


        for (y = 0; y < h; y++) {
            for (x = 0; x < w; x++) {
                Color c = colors.get(map[x][y]);
                dest.getRaster().setPixel(x, y, new int[] {c.getRed(), c.getGreen(), c.getBlue()});
            }
        }

        ImageIO.write(dest, "png", new FileOutputStream("map.png"));
    }

    private static Random randPat = new Random(0);


    private static String [] srcPattern(int size) {
        String [] ret = new String[size*2];
        for (int i = 0; i < size*2; i++) {
            ret[i] = "";
            for (int j = 0; j < size*4; j++) {
                ret[i] += i < size ? "1" : "2";
            }
        }
        return ret;
    }

    private static String [] destPattern(int size) {
        String [] ret = new String[size*2];
        for (int i = 0; i < size*2; i++) {
            ret[i] = "";
            for (int j = 0; j < size*2; j++) {
                //int target = (int)((1 + Math.sin(j * Math.PI * .5/ size) * .4) * size);
                int target = (int)((1 + (Math.cos(j * Math.PI/ size) - 1) * .2) * size);
                ret[i] += (i < target)  ? '1' : '2';
            }
        }

        for (int i = 0; i < size*2; i++) {
            for (int j = 0; j < size*2; j++) {
                ret[i] += ret[size*2 - 1 - i].charAt(size*2 - 1 - j) == '1' ? '2' : '1';
            }
        }
        return ret;
    }
    private static String [] flip(String [] pat) {
        String [] ret = new String[pat.length];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = new StringBuilder(pat[i]).reverse().toString();

        }
        return ret;
    }
    private static String [] rotate(String [] pat) {
        String [] ret = new String[pat[0].length()];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = "";
            for (int j = 0; j < pat.length; j++) {
                ret[i] += pat[j].charAt(i);
            }
        }
        return ret;
    }

    private static boolean findPattern(int [][] map, String [] src, List<String []> dest) {
        for (int y = 0; y < map[0].length - src.length; y++) {
            for (int x = 0; x < map.length - src[0].length(); x++) {
                int c1 = -1, c2 = -1;
                boolean wrong = false;
                for (int y1 = 0; y1 < src.length; y1++) {
                    for (int x1 = 0; x1 < src[0].length(); x1++) {
                        if (src[y1].charAt(x1) == '1') {
                            if (c1 == -1) {
                                c1 = map[x+x1][y+y1];
                            } else {
                                if (c1 != map[x+x1][y+y1]) {
                                    wrong = true;
                                }
                            }
                        }
                        if (src[y1].charAt(x1) == '2') {
                            if (c2 == -1) {
                                c2 = map[x+x1][y+y1];
                            } else {
                                if (c2 != map[x+x1][y+y1]) {
                                    wrong = true;
                                }
                            }
                        }
                        if (c1 != -1 && c1 == c2) wrong = true;
                        if (wrong) break;
                    }
                    if (wrong) break;
                }
                if (!wrong) {
                    System.out.println("Found match at " + x + " " + y);
                    String [] repl = dest.get(randPat.nextInt(dest.size()));
                    for (int y1 = 0; y1 < src.length; y1++) {
                        for (int x1 = 0; x1 < src[0].length(); x1++) {
                            map[x+x1][y+y1] = repl[y1].charAt(x1) == '1' ? c1 : c2;

                        }
                    }
                    return true;
                }
            }
        }           
        return false;
    }

    public static int [][] build(int width, int height) {
        List<Action> actions = new ArrayList<Action>();
        while (height>1 && width>1) {
            if (height % 2 == 1) {
                height--;
                actions.add(Action.ADD_LINE_TOP);
            }
            if (width % 2 == 1) {
                width--;                
                actions.add(Action.ADD_LINE_LEFT);
            }
            if (height%2 == 0 && width%2 == 0) {
                actions.add(Action.DOUBLE_SIZE);
                height /= 2;
                width /= 2;
            }
        }
        actions.add(Action.CREATE);
        Collections.reverse(actions);
        int [][] tab = null;
        for (Action action : actions) {
            if (action == Action.CREATE) {
                tab = new int[width][height];
                if (height >= width) {
                    for (int i = 0; i < height-1; i++) {
                        tab[0][i] = TOP|BOTTOM;
                    }
                    tab[0][height-1] = TOP;
                } else {
                    tab[0][0] = TOP|RIGHT;
                    for (int i = 1; i < width-1; i++) {
                        tab[i][0] = RIGHT|LEFT;
                    }
                    tab[width-1][0] = LEFT;

                }
            }
            if (action == Action.DOUBLE_SIZE) {
                tab = doubleTab(tab);
            }
            if (action == Action.ADD_LINE_TOP) {
                int [][] tab2 = new int[tab.length][tab[0].length+1];
                for (int i = 0; i < tab.length; i++) {
                    for (int j = 0; j < tab[0].length; j++) {
                        tab2[i][j+1] = tab[i][j];
                    }
                }
                tab2[0][0] = BOTTOM|RIGHT;
                for (int i = 1; i < tab.length-1; i++) {
                    tab2[i][0] = RIGHT|LEFT;
                }
                tab2[tab.length-1][0] = TOP|LEFT;
                mirror(tab2);
                tab = tab2;
            }
            if (action == Action.ADD_LINE_LEFT) {
                int [][] tab2 = new int[tab.length+1][tab[0].length];
                for (int i = 0; i < tab.length; i++) {
                    for (int j = 0; j < tab[0].length; j++) {
                        tab2[i+1][j] = tab[i][j];
                    }
                }
                tab2[0][0] = BOTTOM|RIGHT;
                tab2[1][0] |= LEFT;
                tab2[1][0] -= TOP;
                for (int i = 1; i < tab[0].length-1; i++) {
                    tab2[0][i] = TOP|BOTTOM;
                }
                tab2[0][tab[0].length-1] = TOP|BOTTOM;
                flip(tab2);
                tab = tab2;
            }

        }

        return tab;
    }

    private static void mirror(int [][] tab) {
        for (int i = 0; i < tab.length/2; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                int tmp = tab[tab.length - 1 - i][j];
                tab[tab.length - 1 - i][j] = tab[i][j];
                tab[i][j] = tmp;
            }
        }
        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                if ((tab[i][j] & LEFT)!=0 && (tab[i][j] & RIGHT)==0) {
                    tab[i][j] -= LEFT; tab[i][j] |= RIGHT;
                } else if ((tab[i][j] & RIGHT)!=0 && (tab[i][j] & LEFT)==0) {
                    tab[i][j] -= RIGHT; tab[i][j] |= LEFT;
                }
            }
        }
    }

    private static void flip(int [][] tab) {
        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length/2; j++) {
                int tmp = tab[i][tab[0].length - 1 - j];
                tab[i][tab[0].length - 1 - j] = tab[i][j];
                tab[i][j] = tmp;
            }
        }
        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                if ((tab[i][j] & TOP)!=0 && (tab[i][j] & BOTTOM)==0) {
                    tab[i][j] -= TOP; tab[i][j] |= BOTTOM;
                } else if ((tab[i][j] & BOTTOM)!=0 && (tab[i][j] & TOP)==0) {
                    tab[i][j] -= BOTTOM; tab[i][j] |= TOP;
                }
            }
        }
    }


    public static int [][] doubleTab(int [][] tab) {
        boolean [][] shiftTop = new boolean[tab.length][], 
                shiftLeft = new boolean[tab.length][],
                shiftBottom = new boolean[tab.length][],
                shiftRight = new boolean[tab.length][];
        for (int i = 0; i < tab.length; i++) {
            shiftTop[i] = new boolean[tab[i].length];
            shiftLeft[i] = new boolean[tab[i].length];
            shiftBottom[i] = new boolean[tab[i].length];
            shiftRight[i] = new boolean[tab[i].length];
        }

        int x = 0, y = -1;
        for (int i = 0; i < tab.length; i++) {
            if ((tab[i][0] & TOP) != 0) {
                x = i;
            }
        }
        int go = BOTTOM, previous = BOTTOM;
        boolean init = false;
        while (true) {
            if (go == BOTTOM) y++;
            if (go == TOP) y--;
            if (go == LEFT) x--;
            if (go == RIGHT) x++;

            previous = go;

            go = -1;
            if ((tab[x][y] & TOP) != 0 && previous != BOTTOM) go = TOP;
            if ((tab[x][y] & BOTTOM) != 0 && previous != TOP) go = BOTTOM;
            if ((tab[x][y] & LEFT) != 0 && previous != RIGHT) go = LEFT;
            if ((tab[x][y] & RIGHT) != 0 && previous != LEFT) go = RIGHT;
            if (previous == BOTTOM) {
                shiftTop[x][y] = y==0 ? init : shiftBottom[x][y-1];
            }
            if (previous == TOP) {
                shiftBottom[x][y] = shiftTop[x][y+1];
            }
            if (previous == RIGHT) {
                shiftLeft[x][y] = shiftRight[x-1][y];
            }
            if (previous == LEFT) {
                shiftRight[x][y] = shiftLeft[x+1][y];       
            }
            if (go == -1) break;

            if (previous == BOTTOM && go == LEFT) {
                shiftLeft[x][y] = !shiftTop[x][y];
            }
            if (previous == BOTTOM && go == RIGHT) {
                shiftRight[x][y] = shiftTop[x][y];
            }
            if (previous == BOTTOM && go == BOTTOM) {
                shiftBottom[x][y] = shiftTop[x][y];
            }


            if (previous == TOP && go == LEFT) {
                shiftLeft[x][y] = shiftBottom[x][y];
            }
            if (previous == TOP && go == RIGHT) {
                shiftRight[x][y] = !shiftBottom[x][y];
            }
            if (previous == TOP && go == TOP) {
                shiftTop[x][y] = shiftBottom[x][y];
            }

            if (previous == RIGHT && go == TOP) {
                shiftTop[x][y] = !shiftLeft[x][y];
            }
            if (previous == RIGHT && go == BOTTOM) {
                shiftBottom[x][y] = shiftLeft[x][y];
            }
            if (previous == RIGHT && go == RIGHT) {
                shiftRight[x][y] = shiftLeft[x][y];
            }

            if (previous == LEFT && go == TOP) {
                shiftTop[x][y] = shiftRight[x][y];
            }
            if (previous == LEFT && go == BOTTOM) {
                shiftBottom[x][y] = !shiftRight[x][y];
            }
            if (previous == LEFT && go == LEFT) {
                shiftLeft[x][y] = shiftRight[x][y];
            }
        }
        int [][] tab2 = new int[tab.length * 2][];
        for (int i = 0; i < tab2.length; i++) {
            tab2[i] = new int[tab[0].length * 2];
        }

        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                State left = State.NO, right = State.NO, top = State.NO, bottom = State.NO; 
                if ((tab[i][j] & LEFT) != 0) {
                    left = shiftLeft[i][j] ? State.SHIFT : State.YES;
                }
                if ((tab[i][j] & TOP) != 0) {
                    top = shiftTop[i][j] ? State.SHIFT : State.YES;
                }
                if ((tab[i][j] & RIGHT) != 0) {
                    right = shiftRight[i][j] ? State.SHIFT : State.YES;
                }
                if ((tab[i][j] & BOTTOM) != 0) {
                    bottom = shiftBottom[i][j] ? State.SHIFT : State.YES;
                }

                int [] comp = compute(left, top, right, bottom);
                tab2[i*2][j*2] = comp[0];
                tab2[i*2+1][j*2] = comp[1];
                tab2[i*2][j*2+1] = comp[2];
                tab2[i*2+1][j*2+1] = comp[3];
            }
        }
        return tab2;
    }

    private static int [] compute(State left, State top, State right, State bottom) {
        //   |
        // --+
        //
        if (left == State.YES && top == State.SHIFT) {
            return new int[] {LEFT|BOTTOM, TOP|BOTTOM, TOP|RIGHT, TOP|LEFT};// "v^>^";
        }
        if (left == State.SHIFT && top == State.YES) {
            return new int[] {TOP|RIGHT, LEFT|BOTTOM, LEFT|RIGHT, LEFT|TOP}; //"^<>^";
        }
        //   
        // --+
        //   |
        if (left == State.YES && bottom == State.YES) {
            return new int[] {LEFT|RIGHT, LEFT|BOTTOM, RIGHT|BOTTOM, LEFT|TOP}; //">vv<";
        }
        if (left == State.SHIFT && bottom == State.SHIFT) {
            return new int[] {RIGHT|BOTTOM, LEFT|BOTTOM, LEFT|TOP, TOP|BOTTOM}; //">v^v";
        }
        //   |
        //   +--
        //
        if (right == State.SHIFT && top == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM,LEFT|TOP,TOP|RIGHT, LEFT|RIGHT}; //" v<>>";
        }
        if (right == State.YES && top == State.YES) {
            return new int [] {TOP|BOTTOM,RIGHT|BOTTOM,TOP|RIGHT,TOP|LEFT}; //"v>>^";
        }
        //   
        //   +--
        //   |
        if (right == State.YES && bottom == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM, LEFT|RIGHT, TOP|RIGHT, LEFT|BOTTOM}; //"v<>v";
        }
        if (right == State.SHIFT && bottom == State.YES) {
            return new int [] {RIGHT|BOTTOM, LEFT|BOTTOM, TOP|BOTTOM, RIGHT|TOP}; //"v<v^";
        }
        //   
        // --+--
        //   
        if (right == State.YES && left == State.YES) {
            return new int [] {LEFT|BOTTOM, RIGHT|BOTTOM, TOP|RIGHT, LEFT|TOP}; 
        }
        if (right == State.SHIFT && left == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM, LEFT|BOTTOM, LEFT|TOP, RIGHT|TOP}; 
        }
        //   |
        //   +
        //   |
        if (top == State.YES && bottom == State.YES) {
            return new int [] {TOP|RIGHT, LEFT|BOTTOM, BOTTOM|RIGHT, LEFT|TOP}; 
        }
        if (top == State.SHIFT && bottom == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM, LEFT|TOP, RIGHT|TOP, LEFT|BOTTOM}; 
        }
        //
        //   +--
        //
        if (right == State.YES && bottom == State.NO && left == State.NO && top == State.NO) {
            return new int [] {BOTTOM, RIGHT|BOTTOM, TOP|RIGHT, LEFT|TOP}; 
        }
        if (right == State.SHIFT && bottom == State.NO && left == State.NO && top == State.NO) {
            return new int [] {RIGHT|BOTTOM, LEFT|BOTTOM, TOP, RIGHT|TOP}; 
        }

        //   |
        //   +
        //
        if (top == State.YES && bottom == State.NO && left == State.NO && right == State.NO) {
            return new int [] {TOP|RIGHT, LEFT|BOTTOM, RIGHT, LEFT|TOP}; 
        }
        if (top == State.SHIFT && bottom == State.NO && left == State.NO && right == State.NO) {
            return new int [] {BOTTOM|RIGHT, LEFT|TOP, TOP|RIGHT, LEFT}; 
        }
        //   
        //   +
        //   |
        if (bottom == State.YES && top == State.NO && left == State.NO && right == State.NO) {
            return new int [] {RIGHT, LEFT|BOTTOM, BOTTOM|RIGHT, LEFT|TOP}; 
        }
        if (bottom == State.SHIFT && top == State.NO && left == State.NO && right == State.NO) {
            return new int [] {BOTTOM|RIGHT, LEFT, TOP|RIGHT, LEFT|BOTTOM}; 
        }
        //
        // --+
        //
        if (left == State.YES && bottom == State.NO && right == State.NO && top == State.NO) {
            return new int [] {LEFT|BOTTOM, BOTTOM, TOP|RIGHT, LEFT|TOP}; 
        }
        if (left == State.SHIFT && bottom == State.NO && right == State.NO && top == State.NO) {
            return new int [] {BOTTOM|RIGHT, LEFT|BOTTOM, LEFT|TOP, TOP}; 
        }
        return null;
    }
}
Arnaud
fuente
1
A mis ojos, estos no se ven muy realistas. Principalmente por la gran cantidad de líneas rectas ...
Beta Decay
2
@BetaDecay Dado que el OP especifica "a cualquier escala", imagínelo como subregiones de un estado o nación. Entonces puedes tenerlo muy al cuadrado, pero realista, como el mapa del condado de Nebraska .
Geobits
1
@Ambos he agregado algunas "ondas" para corregir las líneas rectas.
Arnaud
@ βετѧΛєҫαγ no parece realista, pero eche un vistazo a la frontera entre EE. UU. y Canadá, está hecho principalmente de pocas líneas rectas, lo mismo con algunas fronteras entre algunos países africanos.
user902383