Gere mapas realistas


25

Eu desenhei este mapa das regiões de uma palavra imaginária em alguns minutos no MS Paint:

meu mapa

Eu acho que ser capaz de gerar mapas como esse programaticamente seria muito legal.

Desafio

Escreva um programa que receba números inteiros positivos We H, e um conjunto não vazio de números inteiros positivos S.

Gere uma imagem em cores verdadeiras padrão com Wpixels de largura por Hpixels de altura.

Para cada inteiro iem S, desenhar uma região plana na imagem cuja área em pixels é proporcional a i, utilizando uma cor diferente a partir de quaisquer regiões vizinhas. Especificamente, o número de pixels na região deve ser W * H * i / sum(S)arredondado para cima ou para baixo para garantir que cada pixel da imagem pertença a uma região .

Uma região plana é um conjunto de pixels com a propriedade de que qualquer pixel na região pode ser alcançado a partir de qualquer outro, permanecendo dentro da região e movendo-se apenas ortogonalmente (e não na diagonal). Meu mapa acima tem 10 regiões planares.

Todos os pixels em uma região plana devem ter a mesma cor, que deve ser diferente da cor de qualquer região vizinha. As regiões podem ter a mesma cor se não forem vizinhas.

Caso contrário, não há limitações sobre como você forma, posiciona ou colore suas regiões. Este é um concurso de popularidade. O objetivo é criar um programa que faça mapas realistas de mundos imaginários, físicos ou políticos, com qualquer geografia, em qualquer escala.

Naturalmente, mostre suas melhores imagens de saída, não apenas seu código.

Detalhes

  • Obtenha informações do arquivo, linha de comando, stdin ou similar. Salve a imagem em qualquer formato padrão ou exiba-a na tela.
  • Seu programa deve ser determinístico para entradas idênticas. Ou seja, a imagem de saída deve ser sempre o mesmo para alguns em particular H, We S. (Observe que Sé um conjunto , não uma lista, portanto sua ordenação não importa.) Caso contrário, você poderá empregar aleatoriedade quando desejado, embora não seja necessário (mas eu sugiro).
  • A geografia da imagem de saída não precisa "escalar" para valores diferentes de Wou H(embora possa). Pode ser completamente diferente.
  • Você pode atribuir cores aleatoriamente, desconsiderando a regra de cores vizinhas, desde que haja pelo menos 32 possibilidades de cores aleatórias, pois seria improvável que dois vizinhos tivessem a mesma cor.
  • As regiões param nos limites da imagem. Não há volta .
  • As regiões podem conter zero pixels (e, portanto, não existem), como seria o caso quando houver mais regiões do que pixels.

Exemplo de entrada

Um envio válido pode ter gerado meu mapa acima com os parâmetros:

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

Esses Svalores são exatamente iguais ao número de pixels em cada região, mas esse não precisa ser o caso. Lembre-se de que Sé um conjunto, portanto nem sempre é classificado.

Respostas:


15

Concordo com os outros. Este foi um desafio surpreendentemente difícil. Parcialmente devido ao requisito de ter pixels adjacentes do mesmo tipo de região, mas também devido ao desafio estético de fazer as regiões parecerem um mapa de países.

Aqui está a minha tentativa ... é terrivelmente ineficiente, mas parece produzir uma saída razoável. Continuando a tendência de usar dados comuns para fins de comparação:

Parâmetros: 380 260 233 420 1300 3511 4772 5089 9507 22107 25117 26744

insira a descrição da imagem aqui

Parâmetros: 380 260 8 5 6 7 8 4 5 6 7 9 4 6 9 5 8 7 5

insira a descrição da imagem aqui

Idade das Trevas de Camelot 213307 1 1 1

insira a descrição da imagem aqui

Meu exemplo maior: (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)

insira a descrição da imagem aqui

Um exemplo com mais países: 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 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;
    }

}

Explicação (dos comentários)

O algoritmo é bastante simples: primeiro inicialize o mapa com pesos aleatórios, escolha pixels de semente aleatórios para cada uma das regiões do país. Em segundo lugar, "cresça" cada região tentando reivindicar pixels adjacentes não reivindicados. Isso ocorre quando o peso do pixel atual excede o peso não reivindicado.

Cada pixel em uma região aumenta seu peso a cada ciclo de crescimento. Além disso, se uma região tiver vizinhos, se a região atual considerada tiver menos pixels do que o desejado, ele roubará pixels do seu vizinho se o vizinho tiver mais pixels do que o desejado. Se a região atual tiver mais pixels que seu vizinho, ele escolherá aleatoriamente um vizinho e fornecerá todos os pixels excedentes para esse vizinho. Quando todas as regiões têm o tamanho correto, a terceira fase ocorre para identificar e converter as regiões que foram divididas e não são mais contínuas.

Somente a maior divisão da região é mantida e as outras divisões são convertidas em pixels não reclamados e a segunda fase é iniciada novamente. Isso se repete até que todos os pixels em uma região sejam adjacentes e todas as regiões tenham o tamanho correto.


Isso é bom ! você poderia explicar um pouco como o seu algoritmo funciona?
Arnaud

1
Ótimo! Eu acho que apenas as cores poderiam ser mais "Terrosas" (a terceira é mais parecida com a Era do Roxo Escuro de Camelot: P). Acho que você esqueceu a imagem do seu último exemplo.
Passatempos de Calvin

@ Os passatempos de Calvin precisam amar a seleção aleatória de cores ... parece que meu computador tem uma propensão para o roxo: P. Opa, esqueci o terceiro exemplo ... irá gerar e atualizar.
Moogie 30/10

12

Este desafio é surpreendentemente difícil. Eu escrevi um gerador de mapas em Python usando Pygame. O programa aumenta a área de cor em espaço livre e resulta em uma imagem que pode parecer um mapa (se você for apertar os olhos).

Meu algoritmo nem sempre completa os países, pois a área restante pode não ter espaço suficiente, mas achei que resultou em um efeito interessante e não vou gastar mais tempo com isso. Os estranhos remendos azuis deixados podem ser considerados grandes lagos, e os traços azuis salpicados entre países são os rios que marcam a fronteira (é um traço, não um inseto!).

Para comparar com o Super Chafouin, usei seus exemplos de parâmetros.

Parâmetros: 380 260 233 420 1300 3511 4772 5089 9507 22107 25117 26744

Teste padrão

Parâmetros: 380 260 8 5 6 7 8 4 5 6 7 9 4 6 9 5 8 7 5

Exemplo 2

Idade das Trevas de Camelot (213307 1 1 1)

Exemplo 3

Meu exemplo maior: (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)

Meu exemplo maior com a Europa Oriental?

Este exemplo se parece um pouco com a Europa Oriental?

Um exemplo com mais países: 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 5 33 11 88 2 7 9 5 6 2 5 7

Mais países com cores suaves

Alterei o gerador de cores com este exemplo para colors = [(80+ri(100), 80+ri(100), 80+ri(100)) for c in counts]obter um intervalo mais suave (e parecido com um mapa).

Código 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)

Não tenho certeza de que respeita a regra "any pixel in the region can be reached from any other by staying within the region and only moving orthogonally". Eu vejo pixels isolados?
Arnaud

O algoritmo atende à regra de continuidade que você citou, mas falha em outras áreas. Esses pixels isolados são "buracos" no país em que o plano de fundo é mostrado. Devido a esse efeito e outros países em cada execução, todos os pixels não serão gerados. Alguns países perdem a maioria dos pixels. Ele não atende a todas as regras especificadas, mas achei que era um resultado interessante. O algoritmo precisaria de trabalho significativo para gerar um mapa perfeito.
Logic Knight

É tecnicamente contra as regras, mas ainda é bem legal. Tentei algo assim depois de fazer a pergunta e ter problemas semelhantes. É mais complicado do que eu pensava!
Passatempos de Calvin

8

Sejamos preguiçosos e adaptemos minha resposta desta pergunta !

  1. O algoritmo calcula um "caminho de cobra" começando no canto superior esquerdo que preenche todo o retângulo. A cobra só pode subir, descer, esquerda, direita.

  2. O caminho da serpente é seguido e é preenchido com a primeira cor, depois a segunda cor, etc ... levando em consideração as porcentagens de cores

  3. Este algoritmo produz muitas linhas retas; para melhorá-lo, eu os detecto e os substituo por "ondas" que mantêm a mesma quantidade de pixels.

Parâmetros: 380 260 233 420 1300 3511 4772 5089 9507 22107 25117 26744

insira a descrição da imagem aqui

Parâmetros: 380 260 8 5 6 7 8 4 5 6 7 9 4 6 9 5 8 7 5

insira a descrição da imagem aqui

Idade das Trevas de Camelot (213307 1 1 1)

insira a descrição da imagem aqui

O 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;
    }
}

1
A meu ver, isso não parece muito realista. Principalmente por causa do grande número de linhas retas ...
Decay Beta

2
@BetaDecay Como o OP especifica "em qualquer escala", imagine-o como sub-regiões de um estado ou nação. Então você pode ter um quadrado, mas realista, como o mapa do condado de Nebraska .
Geobits

1
@ Ambos adicionei algumas "ondas" para corrigir as linhas retas.
Arnaud #

@ parece não realista, mas dê uma olhada na fronteira entre EUA e Canadá, é feita principalmente de poucas linhas retas, o mesmo com algumas fronteiras entre alguns países africanos.
User902383
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.